diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1f7bcea..01c1226 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,19 @@ on:
- master
- main
- dev
+ paths:
+ - 'src/**'
+ - 'tests/**'
+ - 'Cargo.toml'
+ - 'Cargo.lock'
+ - '.github/workflows/ci.yml'
pull_request:
+ paths:
+ - 'src/**'
+ - 'tests/**'
+ - 'Cargo.toml'
+ - 'Cargo.lock'
+ - '.github/workflows/ci.yml'
env:
CARGO_TERM_COLOR: always
@@ -151,4 +163,4 @@ jobs:
- name: sccache stats
if: always()
- run: sccache --show-stats || true
+ run: sccache --show-stats || true
\ No newline at end of file
diff --git a/.github/workflows/code-mirror.yml b/.github/workflows/code-mirror.yml
new file mode 100644
index 0000000..a54e615
--- /dev/null
+++ b/.github/workflows/code-mirror.yml
@@ -0,0 +1,38 @@
+name: Mirror source code to GitVerse
+
+on:
+ push:
+ branches:
+ - '**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: code-mirror-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ mirror:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Mirror all branches
+ env:
+ GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }}
+ GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }}
+ GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }}
+ run: |
+ set -euo pipefail
+ test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; }
+ remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git"
+ git remote add gitverse "$remote"
+ # Use a private ref namespace: Git will not overwrite the checked-out
+ # branch, and broken legacy submodule metadata is not traversed.
+ git fetch --no-recurse-submodules origin '+refs/heads/*:refs/mirror/*'
+ git push --prune --force gitverse 'refs/mirror/*:refs/heads/*'
diff --git a/.github/workflows/release-mirror.yml b/.github/workflows/release-mirror.yml
new file mode 100644
index 0000000..172bc9f
--- /dev/null
+++ b/.github/workflows/release-mirror.yml
@@ -0,0 +1,290 @@
+name: Mirror releases to GitVerse, SourceCraft, and GitLab
+
+on:
+ workflow_run:
+ workflows:
+ - Release
+ types:
+ - completed
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: Git tag of the GitHub release to mirror (for example v0.7.1)
+ required: true
+ type: string
+
+permissions:
+ contents: read
+
+jobs:
+ setup:
+ runs-on: ubuntu-latest
+ outputs:
+ tag: ${{ steps.release.outputs.tag }}
+ found: ${{ steps.release.outputs.found }}
+ steps:
+ - name: Resolve release tag
+ id: release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ MANUAL_TAG: ${{ inputs.tag }}
+ WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
+ WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+ run: |
+ set -euo pipefail
+ tag="$MANUAL_TAG"
+ if [ -z "$tag" ] && [[ "$WORKFLOW_HEAD_BRANCH" == v* ]]; then
+ tag="$WORKFLOW_HEAD_BRANCH"
+ fi
+ if [ -z "$tag" ] && [ -n "$WORKFLOW_HEAD_SHA" ]; then
+ releases=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
+ -H 'Accept: application/vnd.github+json' \
+ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases?per_page=100")
+ while IFS= read -r candidate; do
+ ref=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
+ -H 'Accept: application/vnd.github+json' \
+ "https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${candidate}")
+ sha=$(jq -r '.object.sha' <<< "$ref")
+ if [ "$(jq -r '.object.type' <<< "$ref")" = tag ]; then
+ sha=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
+ -H 'Accept: application/vnd.github+json' \
+ "https://api.github.com/repos/${GITHUB_REPOSITORY}/git/tags/${sha}" | jq -r '.object.sha')
+ fi
+ if [ "$sha" = "$WORKFLOW_HEAD_SHA" ]; then
+ tag="$candidate"
+ break
+ fi
+ done < <(jq -r '.[].tag_name' <<< "$releases")
+ fi
+ if [ -z "$tag" ] || [[ "$tag" != v* ]]; then
+ echo 'No release tag was found; release.yml may have failed before creating a GitHub release.'
+ echo 'found=false' >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ echo "tag=$tag" >> "$GITHUB_OUTPUT"
+ echo 'found=true' >> "$GITHUB_OUTPUT"
+
+ - name: Download GitHub release metadata and assets
+ if: steps.release.outputs.found == 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG: ${{ steps.release.outputs.tag }}
+ run: |
+ set -euo pipefail
+ mkdir -p assets
+ curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
+ -H 'Accept: application/vnd.github+json' \
+ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" > release.json
+ jq -e '.tag_name == $tag' --arg tag "$TAG" release.json >/dev/null
+
+ total=$(jq '.assets | length' release.json)
+ downloaded=0
+ failed=0
+ while IFS=$' ' read -r name url; do
+ if curl -fsSL -L -H "Authorization: Bearer $GH_TOKEN" \
+ -H 'Accept: application/octet-stream' -o "assets/$name" "$url"; then
+ downloaded=$((downloaded+1))
+ else
+ echo "::warning::Failed to download asset: $name"
+ failed=$((failed+1))
+ fi
+ done < <(jq -r '.assets[] | [.name, .browser_download_url] | @tsv' release.json)
+
+ echo "Downloaded ${downloaded}/${total} assets (${failed} failed)"
+ [ "$failed" -eq 0 ] || { echo 'Not all release assets were downloaded.' >&2; exit 1; }
+
+ - name: Upload release metadata artifact
+ if: steps.release.outputs.found == 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-mirror-data
+ path: |
+ release.json
+ assets/*
+ if-no-files-found: error
+
+ mirror-gitverse:
+ needs: setup
+ if: needs.setup.outputs.found == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download release metadata
+ uses: actions/download-artifact@v4
+ with:
+ name: release-mirror-data
+
+ - name: Sync tag and release to GitVerse
+ env:
+ GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }}
+ GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }}
+ GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }}
+ TAG: ${{ needs.setup.outputs.tag }}
+ run: |
+ set -euo pipefail
+ test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; }
+ project="${GITVERSE_ORG}%2F${GITVERSE_REPO}"
+ remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git"
+ git clone --mirror "https://github.com/${GITHUB_REPOSITORY}.git" mirror.git
+ git -C mirror.git remote add gitverse "$remote"
+ git -C mirror.git push gitverse "refs/tags/${TAG}:refs/tags/${TAG}"
+
+ title=$(jq -r '.name // .tag_name' release.json)
+ notes=$(jq -r '.body // ""' release.json)
+ payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \
+ '{name:$title, tag_name:$tag, description:$notes, ref:$tag}')
+
+ http=$(curl -sS -o gitverse-release.json -w '%{http_code}' \
+ -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
+ "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}" || true)
+ if [ "$http" = '200' ]; then
+ echo "GitVerse release ${TAG} already exists; skipping create."
+ else
+ curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
+ -H 'Content-Type: application/json' --data "$payload" \
+ "https://gitverse.ru/api/v4/projects/${project}/releases" || \
+ curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
+ -H 'Content-Type: application/json' --data "$payload" \
+ "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}"
+ fi
+
+ uploaded=0; failed=0
+ for file in assets/*; do
+ [ -f "$file" ] || continue
+ name=$(basename "$file")
+
+ upload_response=$(curl -sS -w "\n%{http_code}" -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
+ -F "file=@$file" "https://gitverse.ru/api/v4/projects/${project}/uploads")
+ http_code=$(echo "$upload_response" | tail -n1)
+ body=$(echo "$upload_response" | sed '$d')
+
+ if [ "$http_code" != "201" ] && [ "$http_code" != "200" ]; then
+ echo "::warning::GitVerse: failed to upload $name (HTTP $http_code)"
+ failed=$((failed+1))
+ continue
+ fi
+
+ url=$(jq -r '.url // .file_path' <<< "$body" 2>/dev/null || echo "")
+ if [ -z "$url" ]; then
+ echo "::warning::GitVerse: failed to parse upload response for $name"
+ failed=$((failed+1))
+ continue
+ fi
+
+ link=$(jq -n --arg name "$name" --arg url "$url" '{name:$name,url:$url}')
+ link_response=$(curl -sS -w "\n%{http_code}" -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
+ -H 'Content-Type: application/json' --data "$link" \
+ "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}/assets/links")
+ link_http=$(echo "$link_response" | tail -n1)
+
+ if [ "$link_http" = "201" ] || [ "$link_http" = "200" ] || [ "$link_http" = "409" ]; then
+ uploaded=$((uploaded+1))
+ else
+ echo "::warning::GitVerse: failed to attach asset $name (HTTP $link_http)"
+ failed=$((failed+1))
+ fi
+ done
+ echo "GitVerse: uploaded ${uploaded}, failed ${failed}"
+ [ "$failed" -eq 0 ] || exit 1
+
+ mirror-sourcecraft:
+ needs: setup
+ if: needs.setup.outputs.found == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download release metadata
+ uses: actions/download-artifact@v4
+ with:
+ name: release-mirror-data
+
+ - name: Sync tag and release to SourceCraft
+ env:
+ SOURCECRAFT_PAT: ${{ secrets.SOURCECRAFT_PAT }}
+ SOURCECRAFT_ORG: ${{ vars.SOURCECRAFT_ORG || 'dexoron-dev' }}
+ SOURCECRAFT_REPO: ${{ vars.SOURCECRAFT_REPO || 'dcr' }}
+ TAG: ${{ needs.setup.outputs.tag }}
+ run: |
+ set -euo pipefail
+ test -n "$SOURCECRAFT_PAT" || { echo 'SOURCECRAFT_PAT is not configured' >&2; exit 1; }
+ remote="https://anyname:${SOURCECRAFT_PAT}@git.sourcecraft.dev/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}.git"
+ git clone "$remote" sourcecraft
+ git -C sourcecraft remote add github "https://github.com/${GITHUB_REPOSITORY}.git"
+ git -C sourcecraft fetch github "refs/tags/${TAG}:refs/tags/${TAG}"
+ git -C sourcecraft push origin "refs/tags/${TAG}"
+
+ title=$(jq -r '.name // .tag_name' release.json)
+ notes=$(jq -r '.body // ""' release.json)
+ payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \
+ '{tag:$tag,title:$title,release_notes:$notes,publish:true}')
+ base="https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}"
+
+ http=$(curl -sS -o /dev/null -w '%{http_code}' \
+ -H "Authorization: Bearer $SOURCECRAFT_PAT" "${base}/releases/tag/${TAG}" || true)
+ if [ "$http" != '200' ]; then
+ curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \
+ -H 'Content-Type: application/json' --data "$payload" "${base}/releases" || \
+ curl -fsSL -X PUT -H "Authorization: Bearer $SOURCECRAFT_PAT" \
+ -H 'Content-Type: application/json' --data "$payload" "${base}/releases/${TAG}" || \
+ echo 'SourceCraft release already exists; continuing with assets.'
+ else
+ echo "SourceCraft release ${TAG} already exists; continuing with assets."
+ fi
+
+ uploaded=0; failed=0
+ for file in assets/*; do
+ [ -f "$file" ] || continue
+ name=$(basename "$file")
+
+ upload_response=$(curl -sS -w "\n%{http_code}" -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \
+ -F "file=@$file" \
+ "${base}/releases/tag/${TAG}/assets")
+ http_code=$(echo "$upload_response" | tail -n1)
+
+ if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then
+ uploaded=$((uploaded+1))
+ elif [ "$http_code" = "409" ]; then
+ uploaded=$((uploaded+1))
+ else
+ echo "::warning::SourceCraft: failed to attach asset $name (HTTP $http_code)"
+ failed=$((failed+1))
+ fi
+ done
+ echo "SourceCraft: uploaded ${uploaded}, failed ${failed}"
+ [ "$failed" -eq 0 ] || exit 1
+
+ mirror-gitlab:
+ needs: setup
+ if: needs.setup.outputs.found == 'true'
+ runs-on: ubuntu-latest
+ continue-on-error: true
+ steps:
+ - name: Download release metadata
+ uses: actions/download-artifact@v4
+ with:
+ name: release-mirror-data
+
+ - name: Sync release to GitLab
+ env:
+ GITLAB_PAT: ${{ secrets.GITLAB_PAT }}
+ GITLAB_PROJECT: ${{ vars.GITLAB_PROJECT || 'dexoron-dev/dcr-tool/dcr' }}
+ TAG: ${{ needs.setup.outputs.tag }}
+ run: |
+ set -euo pipefail
+ test -n "$GITLAB_PAT" || { echo 'GITLAB_PAT is not configured' >&2; exit 0; }
+
+ project_id="${GITLAB_PROJECT//\//%2F}"
+ api="https://gitlab.com/api/v4/projects/${project_id}"
+ title=$(jq -r '.name // .tag_name' release.json)
+ notes=$(jq -r '.body // ""' release.json)
+ payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \
+ '{tag_name:$tag, name:$title, description:$notes}')
+
+ http=$(curl -sS -o /dev/null -w '%{http_code}' \
+ -H "PRIVATE-TOKEN: $GITLAB_PAT" "${api}/releases/${TAG}" || true)
+ if [ "$http" = '200' ]; then
+ echo "GitLab release ${TAG} already exists; skipping"
+ exit 0
+ fi
+
+ echo "Creating release on GitLab..."
+ curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \
+ -H 'Content-Type: application/json' --data "$payload" "${api}/releases" || true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ab1c4a4..cacb427 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -45,6 +45,10 @@ on:
description: "Update Homebrew tap"
type: boolean
default: false
+ run_s3:
+ description: "Mirror Release to Yandex Object Storage (S3)"
+ type: boolean
+ default: false
permissions:
contents: write
@@ -1253,4 +1257,73 @@ jobs:
# sudo snap install --classic --dangerous dcrup_*.snap
#
# publish-snap:
- # if: ...
\ No newline at end of file
+ # if: ...
+
+ # ── Mirror Release to Yandex Object Storage (S3) ─────────────────────────
+ publish-s3:
+ if: |
+ (github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
+ (github.event_name == 'workflow_dispatch' && github.event.inputs.run_s3 == 'true')
+ needs:
+ - setup
+ - create-release
+ - man-pages
+ - build-linux
+ - build-macos
+ - build-bsd
+ - build-windows
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ needs.setup.outputs.tag }}
+
+ - name: Prepare Directory and Download GitHub Release Assets
+ id: fetch-assets
+ shell: bash
+ run: |
+ set -euo pipefail
+ TAG="${{ needs.setup.outputs.tag }}"
+
+ mkdir -p dist
+ cd dist
+
+ echo "Downloading assets from GitHub Release for tag: ${TAG}"
+ gh release download "${TAG}" --dir . --repo "${{ github.repository }}"
+
+ cd ..
+ echo "Archiving source code..."
+ git archive --format=tar.gz --prefix="dcr-${TAG}/" "${TAG}" -o "dist/dcr-src-${TAG}.tar.gz"
+ git archive --format=zip --prefix="dcr-${TAG}/" "${TAG}" -o "dist/dcr-src-${TAG}.zip"
+
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Upload Everything to Yandex Object Storage
+ env:
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ AWS_EC2_METADATA_DISABLED: true
+ shell: bash
+ run: |
+ set -euo pipefail
+ TAG="${{ needs.setup.outputs.tag }}"
+
+ aws s3 sync dist/ "s3://${{ secrets.S3_BUCKET_NAME }}/${TAG}/" \
+ --endpoint-url https://storage.yandexcloud.net \
+ --region ru-central1
+
+ - name: Upload Everything to Cloudflare R2
+ env:
+ AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+ AWS_EC2_METADATA_DISABLED: true
+ shell: bash
+ run: |
+ set -euo pipefail
+ TAG="${{ needs.setup.outputs.tag }}"
+
+ aws s3 sync dist/ "s3://${{ secrets.R2_BUCKET_NAME }}/${TAG}/" \
+ --endpoint-url https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com \
+ --region auto
\ No newline at end of file
diff --git a/.github/workflows/trigger-site-build.yml b/.github/workflows/trigger-site-build.yml
new file mode 100644
index 0000000..cae633d
--- /dev/null
+++ b/.github/workflows/trigger-site-build.yml
@@ -0,0 +1,14 @@
+name: Trigger site rebuild
+
+on:
+ push:
+ branches: [master]
+ paths:
+ - 'docs/**'
+
+jobs:
+ trigger:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Trigger Cloudflare Pages build
+ run: curl -sS -X POST "${{ secrets.CF_PAGES_DEPLOY_HOOK }}"
diff --git a/.gitignore b/.gitignore
index d0a4f5f..8c9d461 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,5 @@ sandbox/**
personal/
.*
-!.gitignore
\ No newline at end of file
+!.gitignore
+!.github/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09c18b7..703e324 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# Changelog
+## [Unreleased]
+
+### RU
+
+**Добавлено:**
+
+- **Зеркала загрузок в `dcrup`** — `--mirror auto|cf|gh|sf|ya` и `DCRUP_MIRROR`.
+ По умолчанию `auto`: **cf → gh → sf → ya** (CloudFlare CDN, GitHub, SourceForge, Yandex CDN).
+
+**Удалено:**
+
+- **`install.sh` / `install.ps1` / `install_bsd.sh`** — устарели, заменены `dcrup`.
+
+### EN
+
+**Added:**
+
+- **Download mirrors in `dcrup`** — `--mirror auto|cf|gh|sf|ya` and `DCRUP_MIRROR`.
+ Default `auto` tries **cf → gh → sf → ya** (CloudFlare CDN, GitHub, SourceForge, Yandex CDN).
+
+**Removed:**
+
+- **`install.sh` / `install.ps1` / `install_bsd.sh`** — superseded by `dcrup`.
+
## [v0.8.5] - 2026-08-08 "Фиксация версии dcr-version и предупреждения совместимости / Config Version Pinning & Compatibility Warnings"
### RU
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 44b179a..1934ac8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,6 +11,7 @@ Thanks for your interest in the project.
```bash
git clone https://github.com/dexoron/dcr.git
+# or a source mirror: GitVerse / SourceCraft / SourceForge / GitLab (see README)
cd dcr
cargo check
```
diff --git a/FAQ.md b/FAQ.md
index 66e3d26..3a48470 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -61,8 +61,19 @@ Available installation methods may vary by project version.
Recommended path:
- check instructions in `README.md`;
+- use `dcrup install stable` (default mirror chain: **cf → gh → sf → ya**);
+- pin a host with `--mirror cf|gh|sf|ya` or `DCRUP_MIRROR` if needed;
- if there is no prebuilt package for your platform, build from source using `cargo build --release`.
+## How do I choose a download mirror?
+
+`dcrup` defaults to `--mirror auto`: CloudFlare CDN, then GitHub, then SourceForge, then Yandex CDN.
+
+```sh
+dcrup install stable --mirror cf
+export DCRUP_MIRROR=ya
+```
+
## How can I get started quickly?
Minimal workflow:
diff --git a/README.md b/README.md
index a73181c..806a8e7 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,9 @@ cargo install dcr
```sh
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
export PATH="$HOME/.dcr/bin:$PATH"
-dcrup install stable
+dcrup install stable # auto: cf → gh → sf → ya
+# dcrup install stable --mirror cf # CloudFlare CDN
+# dcrup install stable --mirror ya # Yandex CDN (RU)
```
@@ -98,7 +100,9 @@ irm https://ps1.dcr-tool.ru | iex
# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
# powershell -File .\dcrup.ps1 self-install
# $env:Path += ";$env:USERPROFILE\.dcr\bin"
-dcrup install stable
+dcrup install stable # auto: cf → gh → sf → ya
+# dcrup install stable --mirror cf # CloudFlare CDN
+# dcrup install stable --mirror ya # Yandex CDN (RU)
```
@@ -216,6 +220,41 @@ Full docs at **[dcr.dexoron.su](https://dcr.dexoron.su)** or in the [`docs/`](do
---
+## Repository Mirrors
+
+The primary repository is hosted on GitHub. Source and release mirrors:
+
+| Region | Mirror | Contents |
+|---|---|---|
+| Worldwide | [GitHub](https://github.com/dexoron/dcr) | Source code and releases |
+| Worldwide | [SourceForge](https://sourceforge.net/projects/dcr-tool/) | Source code and releases |
+| Russia | [GitVerse](https://gitverse.ru/dcr_labs/dcr) | Source code |
+| Russia | [SourceCraft](https://sourcecraft.dev/dexoron-dev/dcr) | Source code |
+
+Release CDN (used by `dcrup`):
+
+| Code | Host | Role |
+|---|---|---|
+| `cf` | [cdn-global.dcr-tool.ru](https://cdn-global.dcr-tool.ru) (CloudFlare) | Worldwide CDN |
+| `gh` | GitHub Releases | Primary host |
+| `sf` | SourceForge | Worldwide fallback |
+| `ya` | [cdn-ru.dcr-tool.ru](https://cdn-ru.dcr-tool.ru) (Yandex) | Paid RU fallback for restrictions |
+
+`dcrup install` defaults to `--mirror auto` and tries **cf → gh → sf → ya**. Override with `--mirror cf|gh|sf|ya` or `DCRUP_MIRROR`.
+
+---
+
+## VS Code Extension
+
+Install the recommended [DCR extension by Dexoron-Labs](https://marketplace.visualstudio.com/items?itemName=Dexoron-Labs.dcr) for VS Code. Do not install Microsoft C/C++ extensions for DCR projects; DCR uses `clangd` instead. If you need debugging with Microsoft's debugger, use MS C/C++ only for its debugger component.
+
+Recommended companion extensions:
+
+- [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)
+- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), or MS C/C++ for debugger support only
+
+---
+
## Contributors
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 0000000..9722dca
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,3 @@
+files:
+ - source: /docs/**/*.mdx
+ translation: /docs-crowdin-export/%locale%/%original_path%/%original_file_name%.%file_extension%
diff --git a/dcrup.ps1 b/dcrup.ps1
index a02786e..094b9a9 100644
--- a/dcrup.ps1
+++ b/dcrup.ps1
@@ -6,6 +6,7 @@ $DcrupBin = if ($env:DCRUP_BIN) { $env:DCRUP_BIN } else { Join-Path $DcrupHom
$DcrupTc = Join-Path $DcrupHome 'toolchains'
$DcrupMeta = Join-Path $DcrupHome 'meta'
$RepoUrl = if ($env:DCRUP_REPO) { $env:DCRUP_REPO } else { 'https://github.com/dexoron/dcr' }
+$DcrupMirror = if ($env:DCRUP_MIRROR) { $env:DCRUP_MIRROR } else { 'auto' }
$ApiLatest = 'https://api.github.com/repos/dexoron/dcr/releases/latest'
$ApiAll = 'https://api.github.com/repos/dexoron/dcr/releases'
$Features = if ($env:DCR_FEATURES) { $env:DCR_FEATURES } else { 'archive' }
@@ -20,7 +21,7 @@ function Show-Usage {
dcrup — install and switch DCR versions (Windows)
Usage:
- dcrup install
[--build|--release] [--force]
+ dcrup install [--build|--release] [--force] [--mirror auto|cf|gh|sf|ya]
dcrup default
dcrup update
dcrup list
@@ -38,15 +39,24 @@ Spec:
night → always build from branch "dev" HEAD
Modes:
- --release download GitHub Release binary (default for stable/dev)
+ --release download prebuilt binary (default for stable/dev)
--build cargo build --release --features archive
night always --build (prebuilt not available)
+Mirrors (prebuilt download only):
+ --mirror auto try cf → gh → sf → ya, first successful download wins (default)
+ --mirror cf download from cdn-global.dcr-tool.ru
+ --mirror gh download from github.com/dexoron/dcr
+ --mirror sf download from sourceforge.net/projects/dcr-tool
+ --mirror ya download from cdn-ru.dcr-tool.ru
+ DCRUP_MIRROR env override (default: auto)
+
Env:
DCRUP_HOME default ~/.dcr
DCRUP_BIN default $DCRUP_HOME/bin
DCR_FEATURES default archive
DCRUP_REPO git/GitHub repo URL
+ DCRUP_MIRROR auto|cf|gh|sf|ya (default: auto)
'@ | Write-Host
}
@@ -123,6 +133,53 @@ function Link-Default([string]$id) {
Ok "default → $id ($dst)"
}
+function Set-Mirror([string]$value) {
+ if ($value -notin @('auto', 'cf', 'gh', 'sf', 'ya')) {
+ Die "invalid --mirror: $value (use auto|cf|gh|sf|ya)"
+ }
+ $script:DcrupMirror = $value
+}
+
+function Get-MirrorUrl([string]$mirror, [string]$tag, [string]$asset) {
+ switch ($mirror) {
+ 'cf' { return "https://cdn-global.dcr-tool.ru/$tag/$asset" }
+ 'gh' { return "https://github.com/dexoron/dcr/releases/download/$tag/$asset" }
+ 'sf' { return "https://sourceforge.net/projects/dcr-tool/files/$tag/$asset/download" }
+ 'ya' { return "https://cdn-ru.dcr-tool.ru/$tag/$asset" }
+ default { Die "invalid mirror: $mirror" }
+ }
+}
+
+function Get-MirrorsToTry {
+ switch ($script:DcrupMirror) {
+ 'auto' { return @('cf', 'gh', 'sf', 'ya') }
+ { $_ -in @('cf', 'gh', 'sf', 'ya') } { return @($script:DcrupMirror) }
+ default { Die "invalid mirror: $($script:DcrupMirror) (use auto|cf|gh|sf|ya)" }
+ }
+}
+
+function Download-FromMirrors([string]$tag, [string]$asset, [string]$destFile) {
+ $tmp = "$destFile.download"
+ foreach ($m in (Get-MirrorsToTry)) {
+ $url = Get-MirrorUrl $m $tag $asset
+ Info "trying mirror $m …"
+ Remove-Item $tmp -ErrorAction SilentlyContinue
+ try {
+ Invoke-WebRequest -Uri $url -OutFile $tmp -UserAgent 'dcrup'
+ $size = (Get-Item $tmp).Length
+ if ($size -gt 100000) {
+ Move-Item $tmp $destFile -Force
+ return $m
+ }
+ Warn "mirror $m returned a too-small file ($size bytes)"
+ } catch {
+ Warn "mirror $m failed"
+ }
+ Remove-Item $tmp -ErrorAction SilentlyContinue
+ }
+ return $null
+}
+
function Fetch-Release($channel, $version) {
if ($version) {
return Invoke-RestMethod -Uri "https://api.github.com/repos/dexoron/dcr/releases/tags/v$version"
@@ -142,8 +199,6 @@ function Install-Prebuilt([switch]$Force) {
$tag = $rel.tag_name
$ver = Normalize-Version $tag
$assetName = "dcr-$triple-$ver.exe"
- $asset = $rel.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
- if (-not $asset) { Die "asset not found: $assetName" }
$id = if ($script:SpecFloating) { Toolchain-Id $ver } else { Toolchain-Id }
$dest = Join-Path $DcrupTc $id
@@ -155,16 +210,19 @@ function Install-Prebuilt([switch]$Force) {
}
Ensure-Dirs
New-Item -ItemType Directory -Force -Path $dest | Out-Null
- Info "downloading $assetName …"
- Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $bin
+ Info "downloading $assetName (mirror=$($script:DcrupMirror)) …"
+ $mirror = Download-FromMirrors $tag $assetName $bin
+ if (-not $mirror) { Die "no mirror reachable for $assetName (try: --mirror cf|gh|sf|ya)" }
+ Info "mirror → $mirror"
@"
channel=$($script:SpecChannel)
version=$ver
tag=$tag
source=release
triple=$triple
+mirror=$mirror
"@ | Set-Content (Join-Path $dest 'dcrup-meta')
- Ok "installed prebuilt $id"
+ Ok "installed prebuilt $id ($triple via $mirror)"
Link-Default $id
}
@@ -270,23 +328,32 @@ function Cmd-Install {
$mode = 'release'
$force = $false
$spec = $null
- foreach ($a in $Args) {
+ $i = 0
+ while ($i -lt $Args.Count) {
+ $a = $Args[$i]
switch -Regex ($a) {
- '^--build$' { $mode = 'build'; continue }
- '^--release$' { $mode = 'release'; continue }
- '^--force$' { $force = $true; continue }
+ '^--build$' { $mode = 'build' }
+ '^--release$' { $mode = 'release' }
+ '^--force$' { $force = $true }
+ '^--mirror$' {
+ if ($i + 1 -ge $Args.Count) { Die '--mirror needs auto|cf|gh|sf|ya' }
+ $i++
+ Set-Mirror $Args[$i]
+ }
+ '^--mirror=(.+)$' { Set-Mirror $Matches[1] }
'^-' { Die "unknown flag: $a" }
default {
if ($spec) { Die "unexpected: $a" }
$spec = $a
}
}
+ $i++
}
- if (-not $spec) { Die 'usage: dcrup install [--build|--release] [--force]' }
+ if (-not $spec) { Die 'usage: dcrup install [--build|--release] [--force] [--mirror auto|cf|gh|sf|ya]' }
Parse-Spec $spec
Ensure-Dirs
if ($script:SpecChannel -eq 'night') { $mode = 'build' }
- Info "install spec=$spec channel=$($script:SpecChannel) mode=$mode"
+ Info "install spec=$spec channel=$($script:SpecChannel) mode=$mode mirror=$($script:DcrupMirror)"
if ($mode -eq 'build') { Install-Build -Force:$force } else { Install-Prebuilt -Force:$force }
Ensure-UserPath
Install-CmdShim
diff --git a/dcrup.sh b/dcrup.sh
index 33cc7e4..7f0bc7a 100755
--- a/dcrup.sh
+++ b/dcrup.sh
@@ -6,6 +6,7 @@ DCRUP_BIN="${DCRUP_BIN:-$DCRUP_HOME/bin}"
DCRUP_TC="${DCRUP_HOME}/toolchains"
DCRUP_META="${DCRUP_HOME}/meta"
REPO_URL="${DCRUP_REPO:-https://github.com/dexoron/dcr}"
+DCRUP_MIRROR="${DCRUP_MIRROR:-auto}"
API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
FEATURES="${DCR_FEATURES:-archive}"
@@ -26,7 +27,7 @@ usage() {
dcrup — install and switch DCR versions
Usage:
- dcrup install [--build|--release] [--force] [--libc gnu|musl|auto]
+ dcrup install [--build|--release] [--force] [--libc gnu|musl|auto] [--mirror auto|cf|gh|sf|ya]
dcrup default
dcrup update
dcrup list
@@ -44,7 +45,7 @@ Spec:
night → always build from branch "dev" HEAD
Modes:
- --release download GitHub Release binary (default for stable/dev)
+ --release download prebuilt binary (default for stable/dev)
--build cargo build --release --features archive
night always --build (prebuilt not available)
@@ -53,12 +54,21 @@ Linux libc (prebuilt triple only):
--libc musl x86_64-unknown-linux-musl
--libc auto musl if host ldd/libc is musl, else gnu
+Mirrors (prebuilt download only):
+ --mirror auto try cf → gh → sf → ya, first successful download wins (default)
+ --mirror cf download from cdn-global.dcr-tool.ru
+ --mirror gh download from github.com/dexoron/dcr
+ --mirror sf download from sourceforge.net/projects/dcr-tool
+ --mirror ya download from cdn-ru.dcr-tool.ru
+ DCRUP_MIRROR env override (default: auto)
+
Env:
DCRUP_HOME default ~/.dcr
DCRUP_BIN default $DCRUP_HOME/bin
DCR_FEATURES default archive
DCRUP_REPO git/GitHub repo URL
DCRUP_LIBC gnu|musl|auto (default: gnu)
+ DCRUP_MIRROR auto|cf|gh|sf|ya (default: auto)
EOF
}
@@ -239,9 +249,57 @@ json_tag() {
printf '%s' "$1" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1
}
-json_asset_url() {
- local json="$1" asset="$2"
- printf '%s' "$json" | sed -n "s#.*\"browser_download_url\"[[:space:]]*:[[:space:]]*\"\([^\"]*/${asset}\)\"#\1#p" | head -n1
+set_mirror() {
+ case "$1" in
+ auto|cf|gh|sf|ya) DCRUP_MIRROR="$1" ;;
+ *) die "invalid --mirror: $1 (use auto|cf|gh|sf|ya)" ;;
+ esac
+}
+
+mirror_url() {
+ local mirror="$1" tag="$2" asset="$3"
+ case "$mirror" in
+ cf) printf 'https://cdn-global.dcr-tool.ru/%s/%s' "$tag" "$asset" ;;
+ gh) printf 'https://github.com/dexoron/dcr/releases/download/%s/%s' "$tag" "$asset" ;;
+ sf) printf 'https://sourceforge.net/projects/dcr-tool/files/%s/%s/download' "$tag" "$asset" ;;
+ ya) printf 'https://cdn-ru.dcr-tool.ru/%s/%s' "$tag" "$asset" ;;
+ *) return 1 ;;
+ esac
+}
+
+mirror_chain() { echo "cf gh sf ya"; }
+
+mirrors_to_try() {
+ case "$DCRUP_MIRROR" in
+ auto) mirror_chain ;;
+ cf|gh|sf|ya) echo "$DCRUP_MIRROR" ;;
+ *) die "invalid mirror: $DCRUP_MIRROR (use auto|cf|gh|sf|ya)" ;;
+ esac
+}
+
+download_from_mirrors() {
+ local tag="$1" asset="$2" dest="$3"
+ local tmp="$dest/dcr.download" m url size
+ USED_MIRROR=""
+ for m in $(mirrors_to_try); do
+ url="$(mirror_url "$m" "$tag" "$asset")"
+ log "trying mirror $m …"
+ rm -f "$tmp"
+ if curl -fL --retry 2 --retry-delay 1 --connect-timeout 10 --max-time 180 \
+ -A "dcrup" "$url" -o "$tmp"; then
+ size="$(wc -c <"$tmp" | tr -d ' ')"
+ if [[ "${size:-0}" -gt 100000 ]]; then
+ mv "$tmp" "$dest/dcr"
+ USED_MIRROR="$m"
+ return 0
+ fi
+ warn "mirror $m returned a too-small file (${size:-0} bytes)"
+ else
+ warn "mirror $m failed"
+ fi
+ rm -f "$tmp"
+ done
+ return 1
}
latest_stable_json() {
@@ -281,7 +339,7 @@ release_json_for_spec() {
}
install_prebuilt() {
- local triple asset tag version url json id dest
+ local triple asset tag version id dest mirror
need_cmd curl
triple="$(detect_triple)"
json="$(release_json_for_spec)"
@@ -292,8 +350,6 @@ install_prebuilt() {
case "$triple" in
*windows*) asset="${asset}.exe" ;;
esac
- url="$(json_asset_url "$json" "$asset")"
- [[ -n "$url" ]] || die "asset not found: $asset (tag $tag)"
id="$(toolchain_id "$version")"
dest="$DCRUP_TC/$id"
@@ -303,9 +359,12 @@ install_prebuilt() {
return 0
fi
- log "downloading $asset …"
mkdir -p "$dest"
- curl -fL "$url" -o "$dest/dcr"
+ log "downloading $asset (mirror=$DCRUP_MIRROR) …"
+ download_from_mirrors "$tag" "$asset" "$dest" \
+ || die "no mirror reachable for $asset (try: --mirror cf|gh|sf|ya)"
+ mirror="$USED_MIRROR"
+ log "mirror → $mirror"
chmod +x "$dest/dcr"
printf '%s\n' \
"channel=$SPEC_CHANNEL" \
@@ -313,9 +372,10 @@ install_prebuilt() {
"tag=$tag" \
"source=release" \
"triple=$triple" \
+ "mirror=$mirror" \
"libc=${DCRUP_LIBC:-gnu}" \
>"$dest/dcrup-meta"
- ok "installed prebuilt $id ($triple)"
+ ok "installed prebuilt $id ($triple via $mirror)"
link_default "$id"
}
@@ -405,6 +465,15 @@ cmd_install() {
libc_cli="${1#--libc=}"
shift
;;
+ --mirror)
+ [[ $# -ge 2 ]] || die "--mirror needs auto|cf|gh|sf|ya"
+ set_mirror "$2"
+ shift 2
+ ;;
+ --mirror=*)
+ set_mirror "${1#--mirror=}"
+ shift
+ ;;
-h|--help) usage; exit 0 ;;
-*) die "unknown flag: $1" ;;
*)
@@ -414,7 +483,7 @@ cmd_install() {
;;
esac
done
- [[ -n "$spec" ]] || die "usage: dcrup install [--build|--release] [--force] [--libc gnu|musl|auto]"
+ [[ -n "$spec" ]] || die "usage: dcrup install [--build|--release] [--force] [--libc gnu|musl|auto] [--mirror auto|cf|gh|sf|ya]"
parse_spec "$spec"
ensure_dirs
@@ -431,7 +500,7 @@ cmd_install() {
fi
export FORCE
- log "install spec=$SPEC_RAW channel=$SPEC_CHANNEL version=${SPEC_VERSION:-∅} mode=$mode libc=$DCRUP_LIBC"
+ log "install spec=$SPEC_RAW channel=$SPEC_CHANNEL version=${SPEC_VERSION:-∅} mode=$mode libc=$DCRUP_LIBC mirror=$DCRUP_MIRROR"
if [[ "$mode" == "build" ]]; then
install_build
else
diff --git a/docs/README.mdx b/docs/README.mdx
index f4e4d7f..fb048f9 100644
--- a/docs/README.mdx
+++ b/docs/README.mdx
@@ -11,7 +11,8 @@ sidebar_label: Introduction
# Installation (dcrup)
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
export PATH="$HOME/.dcr/bin:$PATH"
-dcrup install stable
+dcrup install stable # auto: cf → gh → sf → ya
+# dcrup install stable --mirror cf # CloudFlare / GitHub / SourceForge / Yandex
# Create a project
dcr new my-app
diff --git a/docs/changelog.mdx b/docs/changelog.mdx
index f80dfd9..f306e58 100644
--- a/docs/changelog.mdx
+++ b/docs/changelog.mdx
@@ -4,6 +4,12 @@ sidebar_label: Changelog
# Changelog
+## Unreleased
+
+- **Download mirrors in `dcrup`** — `--mirror auto|cf|gh|sf|ya` and `DCRUP_MIRROR`.
+ Default `auto` tries **cf → gh → sf → ya** (CloudFlare CDN, GitHub, SourceForge, Yandex CDN).
+- **Removed `install.sh` / `install.ps1` / `install_bsd.sh`** — superseded by `dcrup`.
+
## 0.8.5 (2026-08-08)
- **`package.dcr-version`** — optional CLI version pin in `dcr.toml`.
diff --git a/docs/contributing.mdx b/docs/contributing.mdx
index 8883c5b..8a71166 100644
--- a/docs/contributing.mdx
+++ b/docs/contributing.mdx
@@ -8,6 +8,7 @@ sidebar_label: Contributing
```bash
git clone https://github.com/dexoron/dcr
+# or a source mirror: GitVerse / SourceCraft / SourceForge / GitLab (see README)
cd dcr
cargo build
```
diff --git a/docs/faq.mdx b/docs/faq.mdx
index 4680f99..20673a4 100644
--- a/docs/faq.mdx
+++ b/docs/faq.mdx
@@ -61,6 +61,15 @@ Use `kind = "flat-bin"` (NASM `-f bin`) and optional `[archive]` for a FAT disk
See [OS-dev recipe](/docs/recipes/os-flat-bin-archive).
+## How do I choose a download mirror?
+
+`dcrup install` uses `--mirror auto` by default and tries **cf → gh → sf → ya**.
+
+```bash
+dcrup install stable --mirror cf
+export DCRUP_MIRROR=ya
+```
+
## Lock file
`dcr.lock` is created during builds (`dcr build`) when registry dependencies are present. To force an update — delete `dcr.lock` and run `dcr build`.
diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx
index edfb38d..9b9a26f 100644
--- a/docs/getting-started/installation.mdx
+++ b/docs/getting-started/installation.mdx
@@ -45,10 +45,12 @@ After `self-install`, the command is **`dcrup`** (no `.sh` / `.ps1`): shims live
### Common dcrup commands
```sh
-dcrup install stable # latest stable prebuilt
+dcrup install stable # latest stable prebuilt (auto: cf → gh → sf → ya)
dcrup install 0.8.2 # pin → 0.8.2@stable
dcrup install 0.8.2@dev
dcrup install stable --libc musl # Linux: musl asset (default: gnu)
+dcrup install stable --mirror cf # CloudFlare CDN
+dcrup install stable --mirror ya # Yandex CDN (RU fallback)
dcrup install stable --build # cargo build --features archive
dcrup install night # always build from branch dev HEAD
dcrup default 0.8.2
@@ -60,6 +62,22 @@ dcrup which
Layout: `~/.dcr/toolchains//dcr` and `~/.dcr/bin/dcr` → active version.
+### Download mirrors
+
+Prebuilt installs try **cf → gh → sf → ya** unless you pin a host.
+
+| Code | URL | Notes |
+|------|-----|--------|
+| `cf` | `https://cdn-global.dcr-tool.ru//` | CloudFlare CDN (worldwide) |
+| `gh` | `https://github.com/dexoron/dcr/releases/download//` | GitHub Releases |
+| `sf` | `https://sourceforge.net/projects/dcr-tool/files///download` | SourceForge |
+| `ya` | `https://cdn-ru.dcr-tool.ru//` | Yandex CDN (RU fallback) |
+
+```sh
+dcrup install stable --mirror auto # default
+export DCRUP_MIRROR=cf # persist for this shell
+```
+
---
## Arch Linux (AUR)
diff --git a/docs/ide-integration.mdx b/docs/ide-integration.mdx
index ba08f23..7004298 100644
--- a/docs/ide-integration.mdx
+++ b/docs/ide-integration.mdx
@@ -6,6 +6,13 @@ sidebar_label: IDE Integration
## VS Code
+Install the recommended [DCR extension by Dexoron-Labs](https://marketplace.visualstudio.com/items?itemName=Dexoron-Labs.dcr). Do not install Microsoft C/C++ extensions for DCR projects: DCR uses `clangd`. MS C/C++ is acceptable only when you need its debugger.
+
+Recommended companion extensions:
+
+- [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)
+- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), or MS C/C++ for debugger support only
+
```bash
dcr gen vscode
```
diff --git a/docs/reference/environment-variables.mdx b/docs/reference/environment-variables.mdx
index 1c70f9e..406fdbc 100644
--- a/docs/reference/environment-variables.mdx
+++ b/docs/reference/environment-variables.mdx
@@ -57,3 +57,27 @@ export DCR_INDEX_PATH=/custom/path/index.json
```
Default: `~/.dcr/index.json`.
+
+## DCRUP_MIRROR
+
+Download mirror for `dcrup` (prebuilt binaries only).
+
+```bash
+export DCRUP_MIRROR=auto # cf → gh → sf → ya (default)
+export DCRUP_MIRROR=cf # CloudFlare CDN
+export DCRUP_MIRROR=gh # GitHub Releases
+export DCRUP_MIRROR=sf # SourceForge
+export DCRUP_MIRROR=ya # Yandex CDN (RU fallback)
+```
+
+CLI override: `dcrup install stable --mirror cf`.
+
+## DCRUP_HOME / DCRUP_BIN / DCRUP_REPO
+
+Installer layout and source clone URL:
+
+```bash
+export DCRUP_HOME=~/.dcr
+export DCRUP_BIN=~/.dcr/bin
+export DCRUP_REPO=https://github.com/dexoron/dcr
+```
diff --git a/install.ps1 b/install.ps1
deleted file mode 100644
index 11eaca9..0000000
--- a/install.ps1
+++ /dev/null
@@ -1,160 +0,0 @@
-$ErrorActionPreference = 'Stop'
-
-$RepoUrl = 'https://github.com/dexoron/dcr'
-$ApiLatest = 'https://api.github.com/repos/dexoron/dcr/releases/latest'
-$ApiAll = 'https://api.github.com/repos/dexoron/dcr/releases'
-$InstallPath = Join-Path $env:LOCALAPPDATA 'dcr'
-$BinPath = Join-Path $env:USERPROFILE '.local\bin'
-$BinaryPath = Join-Path $InstallPath 'dcr.exe'
-
-function Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Cyan }
-function Ok($msg) { Write-Host "[OK] $msg" -ForegroundColor Green }
-function Warn($msg) { Write-Host "[WARN] $msg" -ForegroundColor Yellow }
-function Fail($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 }
-
-function Require-Cmd($name) {
- if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
- Fail "Not found: $name"
- }
-}
-
-function Select-Channel {
- Write-Host "Choose channel:"
- Write-Host " 1) Latest stable release (default)"
- Write-Host " 2) Latest dev (pre-release)"
- $choice = Read-Host "Enter 1 or 2 [1]"
- if ([string]::IsNullOrWhiteSpace($choice)) { $choice = '1' }
-
- switch ($choice) {
- '1' { return 'stable' }
- '2' { return 'dev' }
- default { Fail 'Unknown option' }
- }
-}
-
-function Select-Mode {
- Write-Host "Choose installation mode:"
- Write-Host " 1) Download prebuilt binary from GitHub Release (recommended)"
- Write-Host " 2) Build from git"
- $choice = Read-Host "Enter 1 or 2 [1]"
- if ([string]::IsNullOrWhiteSpace($choice)) { $choice = '1' }
-
- switch ($choice) {
- '1' { return 'release' }
- '2' { return 'build' }
- default { Fail 'Unknown option' }
- }
-}
-
-function Get-Target {
- $arch = $env:PROCESSOR_ARCHITECTURE
- if ($env:PROCESSOR_ARCHITEW6432) {
- $arch = $env:PROCESSOR_ARCHITEW6432
- }
- switch ($arch) {
- 'AMD64' { return 'x86_64-pc-windows-msvc' }
- 'ARM64' { return 'aarch64-pc-windows-msvc' }
- 'x86' { return 'i686-pc-windows-msvc' }
- default { Fail "Unsupported Windows architecture: $arch" }
- }
-}
-
-function Fetch-ReleaseJson($channel) {
- if ($channel -eq 'dev') {
- Info "Looking for latest dev (pre-release)..."
- $releases = Invoke-RestMethod -Method Get -Uri $ApiAll
- $pre = $releases | Where-Object { $_.prerelease -eq $true } | Select-Object -First 1
- if (-not $pre) { Fail "No dev (pre-release) found on GitHub" }
- return $pre
- } else {
- return Invoke-RestMethod -Method Get -Uri $ApiLatest
- }
-}
-
-function Install-FromRelease {
- param([string]$target, [string]$channel)
-
- New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
-
- $release = Fetch-ReleaseJson $channel
- $tag = $release.tag_name
- $version = $tag.TrimStart('v')
-
- # Имя бинарника: dcr--.exe
- $assetName = "dcr-$target-$version.exe"
-
- Info "Fetching release $tag (channel: $channel)..."
-
- $asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
- if (-not $asset) {
- Fail "Asset $assetName not found in release $tag"
- }
-
- Info "Downloading $assetName..."
- Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $BinaryPath
- Ok "Binary downloaded: $assetName"
-}
-
-function Install-FromSource {
- param([string]$target, [string]$channel)
-
- Require-Cmd 'git'
- Require-Cmd 'cargo'
-
- $tmp = Join-Path $env:TEMP 'dcr-install'
- if (Test-Path $tmp) { Remove-Item -Recurse -Force $tmp }
-
- Info 'Cloning repository...'
- if ($channel -eq 'dev') {
- # Пробуем ветку dev
- $result = git clone --depth 1 --branch dev $RepoUrl $tmp 2>&1
- if ($LASTEXITCODE -ne 0) {
- git clone --depth 1 $RepoUrl $tmp | Out-Null
- }
- } else {
- git clone --depth 1 $RepoUrl $tmp | Out-Null
- }
-
- Info 'Building release binary...'
- Push-Location $tmp
- cargo build --release --target $target
- Pop-Location
-
- New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
- Copy-Item (Join-Path $tmp "target\$target\release\dcr.exe") $BinaryPath -Force
- Ok 'Binary installed from source'
-
- Remove-Item -Recurse -Force $tmp
-}
-
-function Setup-Path {
- New-Item -ItemType Directory -Path $BinPath -Force | Out-Null
- $linkPath = Join-Path $BinPath 'dcr.exe'
- Copy-Item $BinaryPath $linkPath -Force
-
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
- if (-not $userPath) { $userPath = '' }
-
- if (-not ($userPath -split ';' | Where-Object { $_ -eq $BinPath })) {
- $newPath = if ($userPath) { "$userPath;$BinPath" } else { $BinPath }
- [Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
- $env:Path = "$env:Path;$BinPath"
- Warn "Added path to PATH: $BinPath"
- }
-
- Ok "dcr command is available via $linkPath"
-}
-
-Info 'Starting DCR installation'
-$channel = Select-Channel
-$mode = Select-Mode
-$target = Get-Target
-
-if ($mode -eq 'build') {
- Install-FromSource -target $target -channel $channel
-} else {
- Install-FromRelease -target $target -channel $channel
-}
-
-Setup-Path
-Ok 'Installation completed successfully'
\ No newline at end of file
diff --git a/install.sh b/install.sh
deleted file mode 100755
index b600c3b..0000000
--- a/install.sh
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/usr/bin/env bash
-set -Eeuo pipefail
-
-TMPDIR="/tmp/dcr-install"
-INSTALL_PATH="$HOME/.local/share/dcr"
-BINPATH="$HOME/.local/bin"
-MANPATH="${XDG_DATA_HOME:-$HOME/.local/share}/man"
-LOGFILE="$HOME/.cache/dcr-install.log"
-REPO_URL="https://github.com/dexoron/dcr"
-GITHUB_API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
-GITHUB_API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
-
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m'
-
-mkdir -p "$(dirname "$LOGFILE")"
-exec > >(tee -a "$LOGFILE") 2>&1
-
-log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; }
-success() { echo -e "${GREEN}✔ $1${NC}"; }
-warn() { echo -e "${YELLOW}⚠ $1${NC}"; }
-error() { echo -e "${RED}✖ $1${NC}"; }
-
-trap 'error "Error on line $LINENO"; exit 1' ERR
-
-INSTALL_MODE=""
-CHANNEL=""
-
-check_os() {
- case "$(uname -s)" in
- Linux|Darwin|FreeBSD|OpenBSD|NetBSD) ;;
- *) error "Only Linux, macOS, and BSD systems are supported"; exit 1 ;;
- esac
-}
-
-detect_target() {
- local os arch
- os="$(uname -s)"
- arch="$(uname -m)"
-
- case "$os:$arch" in
- Linux:x86_64) TARGET_TRIPLE="x86_64-unknown-linux-gnu" ;;
- Linux:aarch64|Linux:arm64) TARGET_TRIPLE="aarch64-unknown-linux-gnu" ;;
- Linux:i686|Linux:i386) TARGET_TRIPLE="i686-unknown-linux-gnu" ;;
- Linux:armv7l|Linux:armv7) TARGET_TRIPLE="armv7-unknown-linux-gnueabihf" ;;
- Linux:riscv64) TARGET_TRIPLE="riscv64gc-unknown-linux-gnu" ;;
- Darwin:x86_64) TARGET_TRIPLE="x86_64-apple-darwin" ;;
- Darwin:arm64|Darwin:aarch64) TARGET_TRIPLE="aarch64-apple-darwin" ;;
- FreeBSD:x86_64|FreeBSD:amd64) TARGET_TRIPLE="x86_64-unknown-freebsd" ;;
- FreeBSD:aarch64|FreeBSD:arm64) TARGET_TRIPLE="aarch64-unknown-freebsd" ;;
- FreeBSD:i686|FreeBSD:i386) TARGET_TRIPLE="i686-unknown-freebsd" ;;
- OpenBSD:x86_64|OpenBSD:amd64) TARGET_TRIPLE="x86_64-unknown-openbsd" ;;
- OpenBSD:aarch64|OpenBSD:arm64) TARGET_TRIPLE="aarch64-unknown-openbsd" ;;
- OpenBSD:i686|OpenBSD:i386) TARGET_TRIPLE="i686-unknown-openbsd" ;;
- NetBSD:x86_64|NetBSD:amd64) TARGET_TRIPLE="x86_64-unknown-netbsd" ;;
- NetBSD:aarch64|NetBSD:arm64) TARGET_TRIPLE="aarch64-unknown-netbsd" ;;
- NetBSD:i686|NetBSD:i386) TARGET_TRIPLE="i686-unknown-netbsd" ;;
-
- *) error "Unsupported platform: $os/$arch"; exit 1 ;;
- esac
-}
-
-check_common_dependencies() {
- command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
- if [[ "$CHANNEL" == "dev" ]]; then
- if ! command -v python3 >/dev/null 2>&1 && ! command -v jq >/dev/null 2>&1; then
- error "For dev installations, either 'python3' or 'jq' must be installed to parse GitHub API response."
- exit 1
- fi
- fi
-}
-
-check_build_dependencies() {
- command -v git >/dev/null 2>&1 || { error "git is not installed"; exit 1; }
- command -v cargo >/dev/null 2>&1 || { error "cargo is not installed"; exit 1; }
-}
-
-select_channel() {
- echo "Choose channel:"
- echo " 1) Latest stable release (default)"
- echo " 2) Latest dev (pre-release)"
- read -r -p "Enter 1 or 2 [1]: " choice < /dev/tty
-
- case "${choice:-1}" in
- 1) CHANNEL="stable" ;;
- 2) CHANNEL="dev" ;;
- *) error "Unknown option"; exit 1 ;;
- esac
-}
-
-select_install_mode() {
- echo "Choose installation mode:"
- echo " 1) Download prebuilt binary from GitHub Release (recommended)"
- echo " 2) Build from git"
- read -r -p "Enter 1 or 2 [1]: " choice < /dev/tty
-
- case "${choice:-1}" in
- 1) INSTALL_MODE="release" ;;
- 2) INSTALL_MODE="build" ;;
- *) error "Unknown option"; exit 1 ;;
- esac
-}
-
-# Returns JSON of the requested release to stdout
-fetch_release_json() {
- if [[ "$CHANNEL" == "dev" ]]; then
- log "Looking for latest dev (pre-release)..."
- local json result
- json="$(curl -fsSL "$GITHUB_API_ALL")"
- if command -v jq >/dev/null 2>&1; then
- result="$(printf '%s' "$json" | jq -e -c 'map(select(.prerelease == true))[0]')"
- else
- result="$(printf '%s' "$json" | python3 - <<'EOF'
-import sys, json
-releases = json.load(sys.stdin)
-pre = [r for r in releases if r.get("prerelease", False)]
-if not pre:
- print("{}", end="")
-else:
- print(json.dumps(pre[0]), end="")
-EOF
-)"
- fi
-
- if [[ -z "$result" || "$result" == "{}" || "$result" == "null" ]]; then
- error "No dev (pre-release) found on GitHub"
- exit 1
- fi
- printf '%s' "$result"
- else
- curl -fsSL "$GITHUB_API_LATEST"
- fi
-}
-
-download_binary() {
- local release_json tag version asset_name download_url
-
- release_json="$(fetch_release_json)"
-
- tag="$(printf '%s\n' "$release_json" | \
- sed -n 's/.*"tag_name": "\([^"]*\)".*/\1/p' | head -n1)"
- if [[ -z "$tag" ]]; then
- error "Failed to determine release version"
- exit 1
- fi
-
- # Version without leading 'v'
- version="${tag#v}"
- # Binary name: dcr--
- asset_name="dcr-${TARGET_TRIPLE}-${version}"
-
- log "Fetching release ${tag} (channel: ${CHANNEL})..."
-
- download_url="$(printf '%s\n' "$release_json" | \
- sed -n "s#.*\"browser_download_url\": \"\([^\"]*/${asset_name}\)\".*#\1#p" | head -n1)"
-
- if [[ -z "$download_url" ]]; then
- error "Asset ${asset_name} not found in release ${tag}"
- exit 1
- fi
-
- mkdir -p "$INSTALL_PATH"
- curl -fL "$download_url" -o "$INSTALL_PATH/dcr"
- chmod +x "$INSTALL_PATH/dcr"
- success "Binary ${asset_name} downloaded (${tag})"
-}
-
-prepare_sources() {
- log "Fetching sources..."
- rm -rf "$TMPDIR"
- git clone --depth 1 "$REPO_URL" "$TMPDIR"
- if [[ "$CHANNEL" == "dev" ]]; then
- # Clone dev branch if it exists
- git clone --depth 1 --branch dev "$REPO_URL" "$TMPDIR" 2>/dev/null || \
- git clone --depth 1 "$REPO_URL" "$TMPDIR"
- fi
- success "Sources fetched"
-}
-
-build_binary() {
- log "Building release binary..."
- (cd "$TMPDIR" && cargo build --release)
- success "Build completed"
-}
-
-install_built_binary() {
- mkdir -p "$INSTALL_PATH"
- cp "$TMPDIR/target/release/dcr" "$INSTALL_PATH/dcr"
- chmod +x "$INSTALL_PATH/dcr"
- success "Binary installed from source"
-}
-
-install_link() {
- log "Creating symlink..."
- mkdir -p "$BINPATH"
- ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr"
- success "Command 'dcr' added to $BINPATH"
-}
-
-install_man() {
- log "Installing man pages..."
- local man_src="$TMPDIR/man/man1"
- if [[ -d "$man_src" ]]; then
- mkdir -p "$MANPATH/man1"
- cp "$man_src"/*.1 "$MANPATH/man1/"
- success "Man pages installed to $MANPATH/man1"
- fi
-}
-
-check_path() {
- if ! echo "$PATH" | grep -q "$BINPATH"; then
- warn "Directory $BINPATH not found in PATH"
- echo "Add this to ~/.bashrc or ~/.zshrc:"
- echo "export PATH=\"$BINPATH:\$PATH\""
- fi
-}
-
-cleanup() {
- rm -rf "$TMPDIR" 2>/dev/null || true
-}
-
-main() {
- log "Starting DCR installation"
-
- check_os
- detect_target
- select_channel
- check_common_dependencies
- select_install_mode
- cleanup
-
- if [[ "$INSTALL_MODE" == "build" ]]; then
- check_build_dependencies
- prepare_sources
- build_binary
- install_built_binary
- else
- download_binary
- fi
-
- install_link
- install_man
- check_path
- cleanup
-
- success "Installation completed successfully"
- log "Log saved to $LOGFILE"
-}
-
-main "$@"
diff --git a/install_bsd.sh b/install_bsd.sh
deleted file mode 100755
index e793c9b..0000000
--- a/install_bsd.sh
+++ /dev/null
@@ -1,241 +0,0 @@
-#!/bin/sh
-set -eu
-
-TMPDIR="/tmp/dcr-install"
-INSTALL_PATH="$HOME/.local/share/dcr"
-BINPATH="$HOME/.local/bin"
-MANPATH="${XDG_DATA_HOME:-$HOME/.local/share}/man"
-LOGFILE="$HOME/.cache/dcr-install.log"
-REPO_URL="https://github.com/dexoron/dcr"
-GITHUB_API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
-GITHUB_API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
-
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m'
-
-mkdir -p "$(dirname "$LOGFILE")"
-
-log() { printf "${BLUE}[%s]${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOGFILE"; }
-success() { printf "${GREEN}✔ %s${NC}\n" "$1" | tee -a "$LOGFILE"; }
-warn() { printf "${YELLOW}⚠ %s${NC}\n" "$1" | tee -a "$LOGFILE"; }
-error() { printf "${RED}✖ %s${NC}\n" "$1" | tee -a "$LOGFILE" >&2; }
-
-INSTALL_MODE=""
-CHANNEL=""
-
-check_os() {
- case "$(uname -s)" in
- FreeBSD|OpenBSD|NetBSD|DragonFly) ;;
- *) error "Only BSD systems (FreeBSD, OpenBSD, NetBSD, DragonFly) are supported by this script"; exit 1 ;;
- esac
-}
-
-detect_target() {
- local os arch
- os="$(uname -s)"
- arch="$(uname -m)"
-
- case "$os:$arch" in
- FreeBSD:x86_64|FreeBSD:amd64) TARGET_TRIPLE="x86_64-unknown-freebsd" ;;
- FreeBSD:aarch64|FreeBSD:arm64) TARGET_TRIPLE="aarch64-unknown-freebsd" ;;
- FreeBSD:i686|FreeBSD:i386) TARGET_TRIPLE="i686-unknown-freebsd" ;;
- OpenBSD:x86_64|OpenBSD:amd64) TARGET_TRIPLE="x86_64-unknown-openbsd" ;;
- OpenBSD:aarch64|OpenBSD:arm64) TARGET_TRIPLE="aarch64-unknown-openbsd" ;;
- OpenBSD:i686|OpenBSD:i386) TARGET_TRIPLE="i686-unknown-openbsd" ;;
- NetBSD:x86_64|NetBSD:amd64) TARGET_TRIPLE="x86_64-unknown-netbsd" ;;
- NetBSD:aarch64|NetBSD:arm64) TARGET_TRIPLE="aarch64-unknown-netbsd" ;;
- NetBSD:i686|NetBSD:i386) TARGET_TRIPLE="i686-unknown-netbsd" ;;
- DragonFly:x86_64|DragonFly:amd64) TARGET_TRIPLE="x86_64-unknown-dragonfly" ;;
- *) error "Unsupported BSD platform: $os/$arch"; exit 1 ;;
- esac
-}
-
-check_common_dependencies() {
- command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
- if [ "$CHANNEL" = "dev" ]; then
- if ! command -v python3 >/dev/null 2>&1 && ! command -v jq >/dev/null 2>&1; then
- error "For dev installations, either 'python3' or 'jq' must be installed to parse GitHub API response."
- exit 1
- fi
- fi
-}
-
-check_build_dependencies() {
- command -v git >/dev/null 2>&1 || { error "git is not installed"; exit 1; }
- command -v cargo >/dev/null 2>&1 || { error "cargo is not installed"; exit 1; }
-}
-
-select_channel() {
- echo "Choose channel:"
- echo " 1) Latest stable release (default)"
- echo " 2) Latest dev (pre-release)"
- printf "Enter 1 or 2 [1]: "
- read -r choice
-
- case "${choice:-1}" in
- 1) CHANNEL="stable" ;;
- 2) CHANNEL="dev" ;;
- *) error "Unknown option"; exit 1 ;;
- esac
-}
-
-select_install_mode() {
- echo "Choose installation mode:"
- echo " 1) Download prebuilt binary from GitHub Release (recommended)"
- echo " 2) Build from git"
- printf "Enter 1 or 2 [1]: "
- read -r choice
-
- case "${choice:-1}" in
- 1) INSTALL_MODE="release" ;;
- 2) INSTALL_MODE="build" ;;
- *) error "Unknown option"; exit 1 ;;
- esac
-}
-
-fetch_release_json() {
- if [ "$CHANNEL" = "dev" ]; then
- log "Looking for latest dev (pre-release)..."
- local json result
- json="$(curl -fsSL "$GITHUB_API_ALL")"
- if command -v jq >/dev/null 2>&1; then
- result="$(printf '%s' "$json" | jq -e -c 'map(select(.prerelease == true))[0]')"
- else
- result="$(printf '%s' "$json" | python3 - <<'EOF'
-import sys, json
-releases = json.load(sys.stdin)
-pre = [r for r in releases if r.get("prerelease", False)]
-if not pre:
- print("{}", end="")
-else:
- print(json.dumps(pre[0]), end="")
-EOF
-)"
- fi
-
- if [ -z "$result" ] || [ "$result" = "{}" ] || [ "$result" = "null" ]; then
- error "No dev (pre-release) found on GitHub"
- exit 1
- fi
- printf '%s' "$result"
- else
- curl -fsSL "$GITHUB_API_LATEST"
- fi
-}
-
-download_binary() {
- local release_json tag version asset_name download_url
-
- release_json="$(fetch_release_json)"
-
- tag="$(printf '%s\n' "$release_json" | \
- sed -n 's/.*"tag_name": "\([^"]*\)".*/\1/p' | head -n1)"
- if [ -z "$tag" ]; then
- error "Failed to determine release version"
- exit 1
- fi
-
- version="${tag#v}"
- asset_name="dcr-${TARGET_TRIPLE}-${version}"
-
- log "Fetching release ${tag} (channel: ${CHANNEL})..."
-
- download_url="$(printf '%s\n' "$release_json" | \
- sed -n "s#.*\"browser_download_url\": \"\([^\"]*/${asset_name}\)\".*#\1#p" | head -n1)"
-
- if [ -z "$download_url" ]; then
- error "Asset ${asset_name} not found in release ${tag}"
- exit 1
- fi
-
- mkdir -p "$INSTALL_PATH"
- curl -fL "$download_url" -o "$INSTALL_PATH/dcr"
- chmod +x "$INSTALL_PATH/dcr"
- success "Binary ${asset_name} downloaded (${tag})"
-}
-
-prepare_sources() {
- log "Fetching sources..."
- rm -rf "$TMPDIR"
- git clone --depth 1 "$REPO_URL" "$TMPDIR"
- if [ "$CHANNEL" = "dev" ]; then
- git clone --depth 1 --branch dev "$REPO_URL" "$TMPDIR" 2>/dev/null || \
- git clone --depth 1 "$REPO_URL" "$TMPDIR"
- fi
- success "Sources fetched"
-}
-
-build_binary() {
- log "Building release binary..."
- (cd "$TMPDIR" && cargo build --release)
- success "Build completed"
-}
-
-install_built_binary() {
- mkdir -p "$INSTALL_PATH"
- cp "$TMPDIR/target/release/dcr" "$INSTALL_PATH/dcr"
- chmod +x "$INSTALL_PATH/dcr"
- success "Binary installed from source"
-}
-
-install_link() {
- log "Creating symlink..."
- mkdir -p "$BINPATH"
- ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr"
- success "Command 'dcr' added to $BINPATH"
-}
-
-install_man() {
- log "Installing man pages..."
- man_src="$TMPDIR/man/man1"
- if [ -d "$man_src" ]; then
- mkdir -p "$MANPATH/man1"
- cp "$man_src"/*.1 "$MANPATH/man1/"
- success "Man pages installed to $MANPATH/man1"
- fi
-}
-
-check_path() {
- if ! echo "$PATH" | grep -q "$BINPATH"; then
- warn "Directory $BINPATH not found in PATH"
- echo "Add this to ~/.bashrc, ~/.zshrc or ~/.profile:"
- echo "export PATH=\"$BINPATH:\$PATH\""
- fi
-}
-
-cleanup() {
- rm -rf "$TMPDIR" 2>/dev/null || true
-}
-
-main() {
- log "Starting DCR installation for BSD"
-
- check_os
- select_channel
- check_common_dependencies
- detect_target
- select_install_mode
- cleanup
-
- if [ "$INSTALL_MODE" = "build" ]; then
- check_build_dependencies
- prepare_sources
- build_binary
- install_built_binary
- else
- download_binary
- fi
-
- install_link
- install_man
- check_path
- cleanup
-
- success "Installation completed successfully"
- log "Log saved to $LOGFILE"
-}
-
-main "$@"
diff --git a/scripts/sync-releases-to-gitlab.py b/scripts/sync-releases-to-gitlab.py
deleted file mode 100644
index d1e5bed..0000000
--- a/scripts/sync-releases-to-gitlab.py
+++ /dev/null
@@ -1,192 +0,0 @@
-#!/usr/bin/env python3
-"""Sync all GitHub Releases (with assets) to GitLab Releases."""
-
-import json
-import os
-import sys
-import time
-import urllib.error
-import urllib.request
-import uuid
-
-GL_TOKEN = os.environ.get("GL_TOKEN")
-if not GL_TOKEN:
- print("FATAL: GL_TOKEN environment variable not set", file=sys.stderr)
- sys.exit(1)
-
-GL_PROJECT = "dexoron-studio%2Fdcr"
-GL_PROJECT_RAW = "dexoron-studio/dcr"
-GL_API = "https://gitlab.com/api/v4"
-GH_REPO = "dexoron/dcr"
-
-
-def gl_api(method, path, data=None, filepath=None, retries=5):
- url = f"{GL_API}/{path}"
- headers = {"PRIVATE-TOKEN": GL_TOKEN}
-
- if data is not None and filepath is None:
- headers["Content-Type"] = "application/json"
- body = json.dumps(data).encode()
- elif filepath:
- boundary = uuid.uuid4().hex
- with open(filepath, "rb") as f:
- fdata = f.read()
- fname = os.path.basename(filepath)
- parts = []
- parts.append(f"--{boundary}".encode())
- parts.append(f'Content-Disposition: form-data; name="file"; filename="{fname}"'.encode())
- parts.append(b"Content-Type: application/octet-stream")
- parts.append(b"")
- parts.append(fdata)
- parts.append(f"--{boundary}--".encode())
- parts.append(b"")
- body = b"\r\n".join(parts)
- headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
- else:
- body = None
-
- for attempt in range(retries):
- try:
- req = urllib.request.Request(url, data=body, headers=headers, method=method)
- with urllib.request.urlopen(req) as resp:
- text = resp.read().decode()
- return json.loads(text) if text else None
- except urllib.error.HTTPError as e:
- body_text = e.read().decode() if e.fp else ""
- if e.code in (409, 429) and attempt < retries - 1:
- time.sleep(3)
- continue
- if attempt < retries - 1:
- time.sleep(1)
- continue
- print(f" HTTP {e.code}: {body_text[:300]}", file=sys.stderr)
- return None
- except Exception as e:
- if attempt < retries - 1:
- time.sleep(1)
- continue
- print(f" Error: {e}", file=sys.stderr)
- return None
- return None
-
-
-def download_file(url, dest):
- with urllib.request.urlopen(url) as resp:
- with open(dest, "wb") as f:
- f.write(resp.read())
-
-
-# --- MAIN ---
-print("Fetching GitHub releases...")
-with urllib.request.urlopen(f"https://api.github.com/repos/{GH_REPO}/releases?per_page=50") as r:
- gh_releases = json.loads(r.read().decode())
-
-print(f"Found {len(gh_releases)} releases on GitHub")
-
-# Reverse to process oldest first (GitHub returns newest first)
-gh_releases.reverse()
-
-print("Fetching existing GitLab releases and their assets...")
-existing = gl_api("GET", f"projects/{GL_PROJECT}/releases?per_page=100") or []
-existing = existing if isinstance(existing, list) else []
-
-# Build map: tag -> set of existing asset names
-gl_assets = {}
-for rel in existing:
- tag = rel["tag_name"]
- links = rel.get("assets", {}).get("links", []) or rel.get("assets", {}).get("sources", []) or []
- gl_assets[tag] = {l.get("name", "") for l in links if isinstance(l, dict)}
- # Also get the release description to check if it needs updating
- gl_assets[tag + "__desc"] = rel.get("description", "")
-
-print(f"Existing GitLab releases: {len(existing)}")
-
-download_dir = "/tmp/gh-assets"
-os.makedirs(download_dir, exist_ok=True)
-
-# Reverse: process older first so newer releases with many assets get their description too
-# Actually, process in original order
-total = len(gh_releases)
-for i, rel in enumerate(gh_releases):
- tag = rel["tag_name"]
- body = rel.get("body", "") or ""
- assets = rel.get("assets", [])
- if isinstance(assets, dict):
- assets = assets.get("nodes", []) or []
-
- print(f"\n{'='*60}")
- print(f"[{i+1}/{total}] {tag} ({len(assets)} assets)")
-
- # Create or update release
- existing_tag = tag in gl_assets
- if not existing_tag:
- print(f" Creating release...")
- result = gl_api("POST", f"projects/{GL_PROJECT}/releases", data={
- "tag_name": tag,
- "name": rel.get("name", tag),
- "description": body,
- "ref": "master",
- })
- if result is None:
- print(f" FAILED — skip")
- continue
- print(f" OK")
- else:
- # Update description if needed
- existing_desc = gl_assets.get(tag + "__desc", "")
- if body and body != existing_desc:
- print(f" Updating description...")
- gl_api("PUT", f"projects/{GL_PROJECT}/releases/{urllib.request.quote(tag, safe='')}",
- data={"description": body})
-
- existing_links = gl_assets.get(tag, set())
-
- for j, asset in enumerate(assets):
- aname = asset.get("name", "")
- aurl = asset.get("browser_download_url", "")
- if not aurl:
- continue
-
- if aname in existing_links:
- print(f" [{j+1}/{len(assets)}] {aname} — already exists")
- continue
-
- fpath = os.path.join(download_dir, aname)
- print(f" [{j+1}/{len(assets)}] {aname}...", end=" ", flush=True)
-
- # Download from GitHub
- try:
- download_file(aurl, fpath)
- except Exception as e:
- print(f"download failed: {e}")
- continue
-
- # Upload to GitLab
- up = gl_api("POST", f"projects/{GL_PROJECT}/uploads", filepath=fpath)
- if not up or "url" not in up:
- size_mb = os.path.getsize(fpath) / (1024 * 1024)
- print(f"upload failed ({size_mb:.1f} MB)")
- os.remove(fpath)
- continue
-
- file_url = up["url"]
- full_url = f"https://gitlab.com/{GL_PROJECT_RAW}{file_url}"
-
- # Link asset to release
- encoded_tag = urllib.request.quote(tag, safe='')
- link = gl_api("POST",
- f"projects/{GL_PROJECT}/releases/{encoded_tag}/assets/links",
- data={"name": aname, "url": full_url},
- retries=5)
- if link:
- print(f"linked")
- existing_links.add(aname)
- else:
- print(f"link failed")
-
- os.remove(fpath)
-
- time.sleep(0.3)
-
-print(f"\n{'='*60}")
-print("Done!")
\ No newline at end of file