1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 07:39:15 +03:00
This commit is contained in:
Anton Afanasyeu
2026-06-10 20:58:20 +02:00
parent 5e4f208d83
commit e663cb65ab
3 changed files with 172 additions and 74 deletions

View File

@@ -48,19 +48,34 @@ Would you like to reset Gitea repositories state to the default (empty)? [y/N]
After backup (and optional reset), point Gitea at the real remote: After backup (and optional reset), point Gitea at the real remote:
```bash ```bash
export GITEA_ADMIN_TOKEN='your-admin-pat' ./gitea_attach_remote.sh git://10.7.0.10/android_cast
# or: GITEA_MIRROR_URL=git://f0xx.org/android_cast ./gitea_attach_remote.sh
# defaults: git://f0xx.org/android_cast → foxx/android_cast
./gitea_attach_remote.sh
``` ```
### Do I need `GITEA_ADMIN_TOKEN`?
**Usually no** when run **as root on the BE**. The script tries `gitea admin user create-repo` and `gitea admin user generate-access-token` before push.
**Optional** if CLI fails — create in Gitea UI: **Avatar → Settings → Applications → Generate New Token** (scopes: All), then:
```bash
export GITEA_ADMIN_TOKEN='gitea_…'
./gitea_attach_remote.sh git://10.7.0.10/android_cast
```
**Why auth?** Gitea rejects anonymous `git push`. The token (or CLI-generated token) proves write access to `foxx/android_cast`.
**Prerequisite:** user **`foxx`** must exist in Gitea (the script creates the repo, not the user).
**`remote: Not found`** = repo did not exist yet and nothing created it. Re-sync the updated script and re-run.
Local push URL is `http://127.0.0.1:3000/foxx/android_cast.git` (no `/app/.../git/` — nginx strips subpath on :3000).
Override: Override:
```bash ```bash
GITEA_MIRROR_URL='git://f0xx.org/android_cast' \ GITEA_MIRROR_URL='git://10.7.0.10/android_cast' \
GITEA_OWNER='foxx' \ GITEA_OWNER='foxx' GITEA_REPO='android_cast' \
GITEA_REPO='android_cast' \
GITEA_MIRROR_INTERVAL='10m' \
./gitea_attach_remote.sh ./gitea_attach_remote.sh
``` ```

View File

@@ -3,19 +3,17 @@
# Attach canonical git:// remote to Gitea as a pull mirror (no NFS/SSHFS shared folder). # Attach canonical git:// remote to Gitea as a pull mirror (no NFS/SSHFS shared folder).
# Run on Alpine BE after gitea_collect_backup.sh (optional reset) and Gitea is running. # Run on Alpine BE after gitea_collect_backup.sh (optional reset) and Gitea is running.
# #
# Default remote matches docs/INFRA.md: git://f0xx.org/android_cast
#
# Usage: # Usage:
# export GITEA_ADMIN_TOKEN='...' # admin personal access token (recommended)
# ./gitea_attach_remote.sh # ./gitea_attach_remote.sh
# ./gitea_attach_remote.sh git://10.7.0.10/android_cast
# GITEA_MIRROR_URL=git://f0xx.org/android_cast ./gitea_attach_remote.sh
# #
# GITEA_MIRROR_URL=git://f0xx.org/android_cast \ # GITEA_ADMIN_TOKEN is optional on BE when run as root: the script can create a repo
# GITEA_OWNER=foxx GITEA_REPO=android_cast \ # and a short-lived push token via `gitea admin` CLI. Set a token only if CLI fails.
# ./gitea_attach_remote.sh
# #
# Methods (auto): # Methods (auto):
# 1) Gitea API /repos/migrate with mirror=true (fastest ongoing sync in UI) # 1) Gitea API /repos/migrate with mirror=true (ongoing pull sync in UI)
# 2) git clone --mirror + git push --mirror to local Gitea HTTP remote # 2) gitea admin create-repo + git clone --mirror + git push --mirror
# #
set -eu set -eu
@@ -30,6 +28,19 @@ GITEA_MIRROR_INTERVAL="${GITEA_MIRROR_INTERVAL:-10m}"
GITEA_PRIVATE="${GITEA_PRIVATE:-false}" GITEA_PRIVATE="${GITEA_PRIVATE:-false}"
WORK_DIR="${WORK_DIR:-/tmp/gitea-attach-$$}" WORK_DIR="${WORK_DIR:-/tmp/gitea-attach-$$}"
# First positional argument = mirror URL (convenience).
if [ $# -ge 1 ] && [ -n "$1" ]; then
case "$1" in
-h|--help)
sed -n '2,20p' "$0"
exit 0
;;
*)
GITEA_MIRROR_URL="$1"
;;
esac
fi
log() { printf '[gitea-attach] %s\n' "$*"; } log() { printf '[gitea-attach] %s\n' "$*"; }
warn() { printf '[gitea-attach] WARN: %s\n' "$*" >&2; } warn() { printf '[gitea-attach] WARN: %s\n' "$*" >&2; }
die() { printf '[gitea-attach] ERROR: %s\n' "$*" >&2; exit 1; } die() { printf '[gitea-attach] ERROR: %s\n' "$*" >&2; exit 1; }
@@ -38,29 +49,21 @@ need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing command: $1" command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
} }
gitea_api_base() { # Public clone URL (via ROOT_URL + subpath). Local :3000 git/API omit subpath (nginx strips it).
_url="$GITEA_ROOT_URL" gitea_public_repo_url() {
case "$_url" in
*/) _url="${_url%/}" ;;
esac
printf '%s/api/v1' "$_url"
}
gitea_local_http_base() {
printf 'http://127.0.0.1:%s' "${GITEA_HTTP_PORT:-3000}"
}
# ROOT_URL may include subpath; derive path prefix for local git push URL.
gitea_subpath_prefix() {
_root="$GITEA_ROOT_URL" _root="$GITEA_ROOT_URL"
_root="${_root#http://}"
_root="${_root#https://}"
_root="${_root#*@}" # drop user@ if any
_root="${_root#*/}" # drop host
case "$_root" in case "$_root" in
*/*) printf '/%s' "${_root%/}" ;; */) _root="${_root%/}" ;;
*) printf '' ;;
esac esac
printf '%s/%s/%s.git' "$_root" "$GITEA_OWNER" "$GITEA_REPO"
}
gitea_local_git_url() {
printf 'http://127.0.0.1:%s/%s/%s.git' "${GITEA_HTTP_PORT:-3000}" "$GITEA_OWNER" "$GITEA_REPO"
}
gitea_api_base() {
printf '%s' "${GITEA_LOCAL_API:-http://127.0.0.1:3000/api/v1}"
} }
test_remote_reachable() { test_remote_reachable() {
@@ -76,12 +79,73 @@ test_remote_reachable() {
return 1 return 1
} }
resolve_push_token() {
if [ -n "${GITEA_ADMIN_TOKEN:-}" ]; then
printf '%s' "$GITEA_ADMIN_TOKEN"
return 0
fi
_token=""
_token="$(gitea_cli "admin" "user" "generate-access-token" \
"--username" "$GITEA_OWNER" \
"--token-name" "attach-$$" \
"--scopes" "all" \
"--raw" 2>/dev/null)" && [ -n "$_token" ] && printf '%s' "$_token" && return 0
_token="$(gitea_cli "admin" "user" "generate-access-token" \
"--username" "$GITEA_OWNER" \
"--token-name" "attach-$$" \
"--raw" 2>/dev/null)" && [ -n "$_token" ] && printf '%s' "$_token" && return 0
return 1
}
create_repo_via_cli() {
log "creating empty repo $GITEA_OWNER/$GITEA_REPO via gitea admin CLI..."
if gitea_cli "admin" "user" "create-repo" \
"--username" "$GITEA_OWNER" \
"--name" "$GITEA_REPO" \
"--private=$GITEA_PRIVATE" 2>/dev/null; then
log "repo created (admin user create-repo)"
return 0
fi
if gitea_cli "admin" "create-repository" \
"--owner" "$GITEA_OWNER" \
"--name" "$GITEA_REPO" \
"--private=$GITEA_PRIVATE" 2>/dev/null; then
log "repo created (admin create-repository)"
return 0
fi
warn "gitea admin create-repo failed — user '$GITEA_OWNER' must exist (create in Gitea UI first)"
return 1
}
create_repo_via_api() {
[ -n "${GITEA_ADMIN_TOKEN:-}" ] || return 1
need_cmd curl
_api="$(gitea_api_base)"
_http="$(curl -sS -o /tmp/gitea-create-repo.json -w '%{http_code}' \
-X POST "$_api/admin/users/$GITEA_OWNER/repos" \
-H "Authorization: token $GITEA_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$GITEA_REPO\",\"private\":$GITEA_PRIVATE}")"
if [ "$_http" = "201" ] || [ "$_http" = "200" ]; then
log "repo created via API"
return 0
fi
if [ "$_http" = "409" ]; then
log "repo already exists (API 409)"
return 0
fi
warn "API create repo HTTP $_http"
cat /tmp/gitea-create-repo.json >&2 2>/dev/null || true
return 1
}
gitea_owner_uid() { gitea_owner_uid() {
need_cmd curl need_cmd curl
[ -n "${GITEA_ADMIN_TOKEN:-}" ] || return 1 _token="${GITEA_ADMIN_TOKEN:-}"
[ -n "$_token" ] || _token="$(resolve_push_token)" || return 1
_api="$(gitea_api_base)" _api="$(gitea_api_base)"
_uid="$(curl -sS "$_api/users/$GITEA_OWNER" \ _uid="$(curl -sS "$_api/users/$GITEA_OWNER" \
-H "Authorization: token $GITEA_ADMIN_TOKEN" \ -H "Authorization: token $_token" \
| awk -F'"' '/"id"/ { print $4; exit }')" | awk -F'"' '/"id"/ { print $4; exit }')"
[ -n "$_uid" ] || return 1 [ -n "$_uid" ] || return 1
printf '%s' "$_uid" printf '%s' "$_uid"
@@ -89,9 +153,10 @@ gitea_owner_uid() {
migrate_via_api() { migrate_via_api() {
need_cmd curl need_cmd curl
[ -n "${GITEA_ADMIN_TOKEN:-}" ] || return 1 _token="${GITEA_ADMIN_TOKEN:-}"
[ -n "$_token" ] || _token="$(resolve_push_token)" || return 1
_uid="$(gitea_owner_uid)" || die "could not resolve uid for owner $GITEA_OWNER (check token and user exists)" _uid="$(gitea_owner_uid)" || die "could not resolve uid for owner $GITEA_OWNER"
_api="$(gitea_api_base)" _api="$(gitea_api_base)"
_payload=$(cat <<EOF _payload=$(cat <<EOF
@@ -112,10 +177,10 @@ migrate_via_api() {
EOF EOF
) )
log "API migrate (pull mirror) for uid=$_uid ..." log "API migrate (pull mirror) uid=$_uid via $_api ..."
_http="$(curl -sS -o /tmp/gitea-migrate.json -w '%{http_code}' \ _http="$(curl -sS -o /tmp/gitea-migrate.json -w '%{http_code}' \
-X POST "$_api/repos/migrate" \ -X POST "$_api/repos/migrate" \
-H "Authorization: token $GITEA_ADMIN_TOKEN" \ -H "Authorization: token $_token" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$_payload")" -d "$_payload")"
@@ -125,16 +190,18 @@ EOF
printf '\n' printf '\n'
return 0 return 0
fi fi
# Repo may already exist — try sync mirror endpoint
if [ "$_http" = "409" ] || [ "$_http" = "422" ]; then if [ "$_http" = "409" ] || [ "$_http" = "422" ]; then
warn "migrate rejected (HTTP $_http) — repo may exist; try manual mirror settings or delete repo first" warn "migrate rejected HTTP $_http — repo may already exist; use mirror settings or delete repo first"
fi fi
warn "API migrate failed HTTP $_http:" warn "API migrate failed HTTP $_http:"
cat /tmp/gitea-migrate.json >&2 cat /tmp/gitea-migrate.json >&2
return 1 return 1
} }
ensure_gitea_repo_exists() {
create_repo_via_cli || create_repo_via_api || true
}
mirror_via_git() { mirror_via_git() {
need_cmd git need_cmd git
mkdir -p "$WORK_DIR" mkdir -p "$WORK_DIR"
@@ -144,32 +211,19 @@ mirror_via_git() {
rm -rf "$_mirror" rm -rf "$_mirror"
git clone --mirror "$GITEA_MIRROR_URL" "$_mirror" git clone --mirror "$GITEA_MIRROR_URL" "$_mirror"
_prefix="$(gitea_subpath_prefix)" ensure_gitea_repo_exists
_push_url="${GITEA_PUSH_URL:-$(gitea_local_http_base)${_prefix}/${GITEA_OWNER}/${GITEA_REPO}.git}"
log "creating empty repo in Gitea (if needed) via API or assume exists" _push_base="$(gitea_local_git_url)"
if [ -n "${GITEA_ADMIN_TOKEN:-}" ]; then _token="$(resolve_push_token)" || die "need credentials: export GITEA_ADMIN_TOKEN=... or run as root with gitea CLI"
_api="$(gitea_api_base)"
curl -sS -o /dev/null -w '' \
-X POST "$_api/admin/users/$GITEA_OWNER/repos" \
-H "Authorization: token $GITEA_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$GITEA_REPO\",\"private\":$GITEA_PRIVATE}" \
2>/dev/null || true
fi
log "git push --mirror -> $_push_url" _auth_url="${_push_base#http://}"
if [ -n "${GITEA_ADMIN_TOKEN:-}" ]; then _push_url="http://${_token}@${_auth_url}"
_auth_url="$(gitea_local_http_base)${_prefix}/${GITEA_OWNER}/${GITEA_REPO}.git"
_auth_url="${_auth_url#http://}" log "git push --mirror -> $_push_base (localhost, no subpath)"
git -C "$_mirror" push --mirror "http://${GITEA_ADMIN_TOKEN}@${_auth_url}"
else
warn "GITEA_ADMIN_TOKEN unset — push may fail; set token or configure git credentials"
git -C "$_mirror" push --mirror "$_push_url" git -C "$_mirror" push --mirror "$_push_url"
fi
log "one-time mirror push complete" log "one-time mirror push complete"
warn "for ongoing sync, enable pull mirror in Gitea UI or re-run API migrate method" warn "for automatic updates, re-run with API migrate or enable pull mirror in Gitea UI"
rm -rf "$WORK_DIR" rm -rf "$WORK_DIR"
} }
@@ -183,8 +237,22 @@ Ongoing sync (no shared mount):
• Or cron on BE (as git user): • Or cron on BE (as git user):
*/15 * * * * gitea -c $GITEA_APP_INI admin repo-sync-mirrors */15 * * * * gitea -c $GITEA_APP_INI admin repo-sync-mirrors
Clone URL for developers: Clone URL (public):
${GITEA_ROOT_URL}${GITEA_OWNER}/${GITEA_REPO}.git $(gitea_public_repo_url)
EOF
}
print_token_help() {
cat <<'EOF'
About GITEA_ADMIN_TOKEN (optional on BE):
• What: a Personal Access Token for your Gitea admin/owner user.
• Where: log in to Gitea UI → top-right avatar → Settings → Applications
→ Generate New Token → name it e.g. "attach-script" → scopes: all (or repo write).
• Why: HTTP git push and REST API require authentication. Without a token the script
tries `gitea admin user generate-access-token` on the server (works when run as root).
• You do NOT need a token if `gitea admin create-repo` + generate-access-token succeed.
EOF EOF
} }
@@ -193,13 +261,13 @@ EOF
gitea_load_config || exit 1 gitea_load_config || exit 1
log "Gitea ROOT_URL=$GITEA_ROOT_URL" log "Gitea ROOT_URL=$GITEA_ROOT_URL (public)"
log "local API=$(gitea_api_base) local git=$(gitea_local_git_url)"
log "target repo: $GITEA_OWNER/$GITEA_REPO" log "target repo: $GITEA_OWNER/$GITEA_REPO"
log "mirror from: $GITEA_MIRROR_URL" log "mirror from: $GITEA_MIRROR_URL"
if ! test_remote_reachable "$GITEA_MIRROR_URL"; then if ! test_remote_reachable "$GITEA_MIRROR_URL"; then
warn "remote not reachable from this host" warn "remote not reachable from this host — check git:// daemon / firewall"
warn "ensure git-daemon client access to f0xx.org:9418 or set GITEA_MIRROR_URL to https/ssh URL"
fi fi
if command -v rc-service >/dev/null 2>&1; then if command -v rc-service >/dev/null 2>&1; then
@@ -216,6 +284,9 @@ if migrate_via_api; then
fi fi
log "falling back to git clone --mirror + push --mirror" log "falling back to git clone --mirror + push --mirror"
mirror_via_git if ! mirror_via_git; then
print_token_help
exit 1
fi
enable_mirror_sync_cron_hint enable_mirror_sync_cron_hint
log "done (git mirror push — configure pull mirror in UI for automatic updates)" log "done (git mirror push)"

View File

@@ -70,6 +70,18 @@ gitea_load_config() {
export GITEA_APP_INI GITEA_REPO_ROOT GITEA_APP_DATA GITEA_ROOT_URL GITEA_HTTP_PORT export GITEA_APP_INI GITEA_REPO_ROOT GITEA_APP_DATA GITEA_ROOT_URL GITEA_HTTP_PORT
export GITEA_DOMAIN GITEA_DB_TYPE GITEA_DB_HOST GITEA_DB_NAME GITEA_DB_USER GITEA_DB_PASS export GITEA_DOMAIN GITEA_DB_TYPE GITEA_DB_HOST GITEA_DB_NAME GITEA_DB_USER GITEA_DB_PASS
export GITEA_DB_PATH GITEA_LFS_PATH GITEA_LOG_ROOT export GITEA_DB_PATH GITEA_LFS_PATH GITEA_LOG_ROOT
# External URL (ROOT_URL) includes nginx subpath; direct :3000 API/git do not.
GITEA_HTTP_SUBPATH="${GITEA_HTTP_SUBPATH:-}"
if [ -z "$GITEA_HTTP_SUBPATH" ] && [ -n "$GITEA_ROOT_URL" ]; then
_p="${GITEA_ROOT_URL#*://}"
_p="${_p#*/}"
case "$_p" in
*/*) GITEA_HTTP_SUBPATH="/${_p%/}" ;;
esac
fi
GITEA_LOCAL_API="${GITEA_LOCAL_API:-http://127.0.0.1:${GITEA_HTTP_PORT}/api/v1}"
export GITEA_HTTP_SUBPATH GITEA_LOCAL_API
} }
# Must be absolute, no .., and under allowed prefixes (Gitea-owned paths only). # Must be absolute, no .., and under allowed prefixes (Gitea-owned paths only).