Compare commits
85
Commits
ce9a8e8c5b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39387b5e0f | ||
|
|
e67ac50c61 | ||
|
|
3ebbeba672 | ||
|
|
07394769ca | ||
|
|
ae939c4085 | ||
|
|
28996eff57 | ||
|
|
93efe0d607 | ||
|
|
79861c72f3 | ||
|
|
2422ce1385 | ||
|
|
6fc6c3b84d | ||
|
|
e6522eadec | ||
|
|
2dcfaafc87 | ||
|
|
33e4f64d69 | ||
|
|
cbfc28c2dd | ||
|
|
a212be09c3 | ||
|
|
8a9241f0ff | ||
|
|
5893346625 | ||
|
|
5847b81dfd | ||
|
|
52207598b9 | ||
|
|
c57f760fdc | ||
|
|
57fd74f5af | ||
|
|
4ed7a9d2d5 | ||
|
|
9ba1d7e9db | ||
|
|
4b5ca9f6ea | ||
|
|
2517b31336 | ||
|
|
b671c1b2ec | ||
|
|
d0c444e63f | ||
|
|
0f89a3d534 | ||
|
|
d84b958937 | ||
|
|
866d895357 | ||
|
|
2005534b12 | ||
|
|
a3642c5159 | ||
|
|
8aaaf993ac | ||
|
|
09a24c2f16 | ||
|
|
0be27b15e9 | ||
|
|
9714bfca2e | ||
|
|
93288be7e2 | ||
|
|
e3874b2ebe | ||
|
|
343c2ef68b | ||
|
|
164d5e8891 | ||
|
|
24fe5a3177 | ||
|
|
70f3bfbd85 | ||
|
|
2c93a2c7fd | ||
|
|
5c3a38b9f0 | ||
|
|
24b62b7415 | ||
|
|
52604b3ba6 | ||
|
|
3d9df0793a | ||
|
|
14541062fc | ||
|
|
d954c605b0 | ||
|
|
5edfed7735 | ||
|
|
910a49f12f | ||
|
|
22990b6583 | ||
|
|
b4ac5a4de0 | ||
|
|
daf0ed11e4 | ||
|
|
0fe0c74235 | ||
|
|
2e7e073b63 | ||
|
|
f27fdad711 | ||
|
|
e965c2bd76 | ||
|
|
c7cc7176f0 | ||
|
|
ad5440a58b | ||
|
|
a55f6c430a | ||
|
|
3b0689dfd9 | ||
|
|
83d62b05fd | ||
|
|
423b295acf | ||
|
|
25dc4251de | ||
|
|
0a32ab7844 | ||
|
|
24e59a280c | ||
|
|
253ee7587b | ||
|
|
505a342417 | ||
|
|
ddfae32987 | ||
|
|
1b4036a0c2 | ||
|
|
a49f8c3533 | ||
|
|
c9d1eac7f2 | ||
|
|
7ed376e9b7 | ||
|
|
435992a4f0 | ||
|
|
9d3801494a | ||
|
|
eb794e61f9 | ||
|
|
5ace213bd4 | ||
|
|
1d386e6a58 | ||
|
|
778d06b0b8 | ||
|
|
63faa9b1bd | ||
|
|
4199f42f70 | ||
|
|
2c51ab5faa | ||
|
|
6dff335ac7 | ||
|
|
85b13d07a7 |
+46
-13
@@ -1356,6 +1356,29 @@ _authelia_gen_temp_password() {
|
|||||||
| fold -w1 | shuf | tr -d '\n'
|
| fold -w1 | shuf | tr -d '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Lets the admin type a specific password instead of always getting an
|
||||||
|
# auto-generated one — same masked-input, "[Enter = auto-generate]"
|
||||||
|
# convention services/backup.sh/borg-backup.sh/koha.sh already use for their
|
||||||
|
# own passwords, rather than inventing a separate typed-vs-generated menu
|
||||||
|
# choice here. Sets two out-params (not `local` — read them after the call
|
||||||
|
# returns, same convention as OIDC_CLIENT_SECRET_PLAIN elsewhere in this
|
||||||
|
# file): AUTHELIA_CHOSEN_PASSWORD (the plaintext, never written to disk —
|
||||||
|
# only its argon2 hash is) and AUTHELIA_PASSWORD_AUTO_GENERATED (so callers
|
||||||
|
# can word their own "here's the password" message correctly either way).
|
||||||
|
_authelia_prompt_password() {
|
||||||
|
AUTHELIA_CHOSEN_PASSWORD=""
|
||||||
|
AUTHELIA_PASSWORD_AUTO_GENERATED=false
|
||||||
|
local _pw=""
|
||||||
|
if [ "$UNATTENDED" != true ]; then
|
||||||
|
read -rsp " Password [Enter = auto-generate]: " _pw; echo
|
||||||
|
fi
|
||||||
|
if [ -z "$_pw" ]; then
|
||||||
|
_pw="$(_authelia_gen_temp_password)"
|
||||||
|
AUTHELIA_PASSWORD_AUTO_GENERATED=true
|
||||||
|
fi
|
||||||
|
AUTHELIA_CHOSEN_PASSWORD="$_pw"
|
||||||
|
}
|
||||||
|
|
||||||
# Adds a new user to an EXISTING Authelia instance's users.yml — the scripted
|
# Adds a new user to an EXISTING Authelia instance's users.yml — the scripted
|
||||||
# version of the manual "generate a hash, paste a users.yml block, restart"
|
# version of the manual "generate a hash, paste a users.yml block, restart"
|
||||||
# steps this file's own generated README already documents. Non-destructive:
|
# steps this file's own generated README already documents. Non-destructive:
|
||||||
@@ -1376,8 +1399,9 @@ add_authelia_user() {
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " Add a new user to this Authelia instance."
|
echo " Add a new user to this Authelia instance."
|
||||||
echo " They log in with their username (not email). A temporary password"
|
echo " They log in with their username (not email). You'll set a password"
|
||||||
echo " is generated below — hand it to them directly. \"Forgot Password\""
|
echo " next — type your own or leave it blank to auto-generate one — shown"
|
||||||
|
echo " once here either way, never stored in plaintext. \"Forgot Password\""
|
||||||
echo " and Authelia's own Settings → Change Password both require working"
|
echo " and Authelia's own Settings → Change Password both require working"
|
||||||
echo " SMTP (both email a one-time code), so until that's fixed, use this"
|
echo " SMTP (both email a one-time code), so until that's fixed, use this"
|
||||||
echo " menu's \"Edit an existing user\" → \"Reset password\" for future resets."
|
echo " menu's \"Edit an existing user\" → \"Reset password\" for future resets."
|
||||||
@@ -1399,9 +1423,9 @@ add_authelia_user() {
|
|||||||
local NEW_ADMIN_YN=""
|
local NEW_ADMIN_YN=""
|
||||||
prompt_yn " Grant admin group membership too? (y/n):" "n" NEW_ADMIN_YN
|
prompt_yn " Grant admin group membership too? (y/n):" "n" NEW_ADMIN_YN
|
||||||
|
|
||||||
log_info "Generating temporary password + hash..."
|
_authelia_prompt_password
|
||||||
local TEMP_PASS NEW_HASH
|
local TEMP_PASS="$AUTHELIA_CHOSEN_PASSWORD" NEW_HASH
|
||||||
TEMP_PASS="$(_authelia_gen_temp_password)"
|
log_info "Generating password hash..."
|
||||||
NEW_HASH=$(docker run --rm authelia/authelia:4.39.20 \
|
NEW_HASH=$(docker run --rm authelia/authelia:4.39.20 \
|
||||||
authelia crypto hash generate argon2 --password "$TEMP_PASS" 2>/dev/null \
|
authelia crypto hash generate argon2 --password "$TEMP_PASS" 2>/dev/null \
|
||||||
| grep -oP '(?<=Digest: ).*')
|
| grep -oP '(?<=Digest: ).*')
|
||||||
@@ -1439,8 +1463,12 @@ ${GROUPS_BLOCK}"
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " New user: ${NEW_USERNAME}"
|
echo " New user: ${NEW_USERNAME}"
|
||||||
echo " Temp password: ${TEMP_PASS}"
|
if [ "$AUTHELIA_PASSWORD_AUTO_GENERATED" = true ]; then
|
||||||
|
echo " Temp password: ${TEMP_PASS}"
|
||||||
|
else
|
||||||
|
echo " Password: ${TEMP_PASS} (the one you just typed)"
|
||||||
|
fi
|
||||||
echo " Give this to them directly (it's shown once, nothing stores it in"
|
echo " Give this to them directly (it's shown once, nothing stores it in"
|
||||||
echo " plaintext). They can log in with it as-is and keep using it, or"
|
echo " plaintext). They can log in with it as-is and keep using it, or"
|
||||||
echo " change it themselves from Authelia's Settings page — but that page"
|
echo " change it themselves from Authelia's Settings page — but that page"
|
||||||
@@ -2697,7 +2725,7 @@ _authelia_manage_one_user() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo " Editing user: $TARGET (admin: $IS_ADMIN, 2FA-exempt: $IS_EXEMPT)"
|
echo " Editing user: $TARGET (admin: $IS_ADMIN, 2FA-exempt: $IS_EXEMPT)"
|
||||||
echo " 1) Edit email / display name"
|
echo " 1) Edit email / display name"
|
||||||
echo " 2) Reset password"
|
echo " 2) Set/reset password (type your own, or auto-generate)"
|
||||||
echo " 3) Reset 2FA device (they register a new one on next login)"
|
echo " 3) Reset 2FA device (they register a new one on next login)"
|
||||||
if [ "$IS_EXEMPT" = "yes" ]; then
|
if [ "$IS_EXEMPT" = "yes" ]; then
|
||||||
echo " 4) Restore the 2FA requirement for this user"
|
echo " 4) Restore the 2FA requirement for this user"
|
||||||
@@ -2729,9 +2757,9 @@ _authelia_manage_one_user() {
|
|||||||
log_success "Updated $TARGET's email/display name."
|
log_success "Updated $TARGET's email/display name."
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
log_info "Generating a new temporary password + hash..."
|
_authelia_prompt_password
|
||||||
local NEW_TEMP_PASS NEW_HASH
|
local NEW_TEMP_PASS="$AUTHELIA_CHOSEN_PASSWORD" NEW_HASH
|
||||||
NEW_TEMP_PASS="$(_authelia_gen_temp_password)"
|
log_info "Generating password hash..."
|
||||||
NEW_HASH=$(docker run --rm authelia/authelia:4.39.20 \
|
NEW_HASH=$(docker run --rm authelia/authelia:4.39.20 \
|
||||||
authelia crypto hash generate argon2 --password "$NEW_TEMP_PASS" 2>/dev/null \
|
authelia crypto hash generate argon2 --password "$NEW_TEMP_PASS" 2>/dev/null \
|
||||||
| grep -oP '(?<=Digest: ).*')
|
| grep -oP '(?<=Digest: ).*')
|
||||||
@@ -2740,8 +2768,13 @@ _authelia_manage_one_user() {
|
|||||||
else
|
else
|
||||||
_authelia_set_user_field "$USERS_FILE" "$START" "$END" "password" " password: \"${NEW_HASH}\""
|
_authelia_set_user_field "$USERS_FILE" "$START" "$END" "password" " password: \"${NEW_HASH}\""
|
||||||
chown 1000:1000 "$USERS_FILE" 2>/dev/null || true
|
chown 1000:1000 "$USERS_FILE" 2>/dev/null || true
|
||||||
log_success "Password reset for $TARGET."
|
if [ "$AUTHELIA_PASSWORD_AUTO_GENERATED" = true ]; then
|
||||||
echo " New password: ${NEW_TEMP_PASS}"
|
log_success "Password reset for $TARGET (auto-generated)."
|
||||||
|
echo " New password: ${NEW_TEMP_PASS}"
|
||||||
|
else
|
||||||
|
log_success "Password set for $TARGET."
|
||||||
|
echo " Password: ${NEW_TEMP_PASS} (the one you just typed)"
|
||||||
|
fi
|
||||||
echo " Give this to them directly — shown once, not stored in plaintext anywhere."
|
echo " Give this to them directly — shown once, not stored in plaintext anywhere."
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|||||||
+321
-4
@@ -453,6 +453,11 @@ _gitea_remove_sync_timer() {
|
|||||||
# reconfigure of an existing one. Always asked (matches pstn-trunk.sh's
|
# reconfigure of an existing one. Always asked (matches pstn-trunk.sh's
|
||||||
# international-calling step reasoning: a live-editable extra, not a
|
# international-calling step reasoning: a live-editable extra, not a
|
||||||
# structural setting tied exclusively to fresh installs).
|
# structural setting tied exclusively to fresh installs).
|
||||||
|
#
|
||||||
|
# Sets _GITEA_SYNC_FLAG as an out-param (not `local` — read it after the
|
||||||
|
# call returns, same convention as CADDY_SERVICE_CONFIGURED) so the caller
|
||||||
|
# can decide whether the real-time webhook offer even makes sense for the
|
||||||
|
# direction just chosen.
|
||||||
_gitea_run_sync_direction_step() {
|
_gitea_run_sync_direction_step() {
|
||||||
local DIR="$1"
|
local DIR="$1"
|
||||||
|
|
||||||
@@ -463,12 +468,13 @@ _gitea_run_sync_direction_step() {
|
|||||||
echo " 3) Both directions"
|
echo " 3) Both directions"
|
||||||
local _DIR_CHOICE=""
|
local _DIR_CHOICE=""
|
||||||
prompt_text " Choice [1]:" "1" _DIR_CHOICE
|
prompt_text " Choice [1]:" "1" _DIR_CHOICE
|
||||||
local FLAG="" DIR_DESC=""
|
local DIR_DESC=""
|
||||||
case "$_DIR_CHOICE" in
|
case "$_DIR_CHOICE" in
|
||||||
2) FLAG="--push-only"; DIR_DESC="Gitea -> GitHub only" ;;
|
2) _GITEA_SYNC_FLAG="--push-only"; DIR_DESC="Gitea -> GitHub only" ;;
|
||||||
3) FLAG=""; DIR_DESC="both directions" ;;
|
3) _GITEA_SYNC_FLAG=""; DIR_DESC="both directions" ;;
|
||||||
*) FLAG="--pull-only"; DIR_DESC="GitHub -> Gitea only" ;;
|
*) _GITEA_SYNC_FLAG="--pull-only"; DIR_DESC="GitHub -> Gitea only" ;;
|
||||||
esac
|
esac
|
||||||
|
local FLAG="$_GITEA_SYNC_FLAG"
|
||||||
log_info "Sync direction: $DIR_DESC"
|
log_info "Sync direction: $DIR_DESC"
|
||||||
|
|
||||||
_gitea_remove_sync_timer
|
_gitea_remove_sync_timer
|
||||||
@@ -519,6 +525,250 @@ _gitea_run_sync_direction_step() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Real-time sync: a GitHub webhook receiver, not just the timer above ────
|
||||||
|
# The timer above polls on a fixed schedule (default 6h) — fine for a slow
|
||||||
|
# backup cadence, but a genuine "GitHub -> Gitea in real time" ask needs
|
||||||
|
# GitHub to tell Gitea the moment something changes instead of Gitea finding
|
||||||
|
# out up to one interval late. GitHub's own webhook (repo Settings ->
|
||||||
|
# Webhooks) is the standard way to do that: it POSTs a JSON payload the
|
||||||
|
# instant someone pushes. This writes a tiny stdlib-only Python HTTP server
|
||||||
|
# to receive it — python3 is already a hard dependency of this directory's
|
||||||
|
# gitea-github-sync.sh itself (used there for JSON parsing), so this adds
|
||||||
|
# no new dependency — running under its own persistent systemd service,
|
||||||
|
# and wires it up to Caddy the same way every other web-facing piece of
|
||||||
|
# this install does.
|
||||||
|
#
|
||||||
|
# Deliberately NOT a Docker container: it just shells out to the existing
|
||||||
|
# gitea-github-sync.sh sitting right next to it in $DIR, the same way the
|
||||||
|
# timer's own systemd service does — no image to build/pull for what's
|
||||||
|
# fundamentally a few lines of stdlib HTTP handling.
|
||||||
|
_gitea_write_webhook_receiver() {
|
||||||
|
local DIR="$1"
|
||||||
|
cat > "$DIR/gitea-github-webhook.py" << 'PYEOF'
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Gitea <-> GitHub webhook receiver — triggers an immediate, single-repo
|
||||||
|
mirror sync (gitea-github-sync.sh --repo owner/name --pull-only) the moment
|
||||||
|
GitHub POSTs a push event, instead of waiting for the scheduled timer.
|
||||||
|
|
||||||
|
Written by services/gitea.sh — re-run 'sudo ./setup.sh gitea' (Update mode
|
||||||
|
is fine) to regenerate this file rather than hand-editing it; a hand edit
|
||||||
|
survives until the next Update-mode rerun overwrites it again.
|
||||||
|
|
||||||
|
WEBHOOK_SECRET is read from .env in this same directory at every request,
|
||||||
|
never taken from the environment/systemd unit — /etc/systemd/system/*.service
|
||||||
|
files are world-readable, and .env (chmod 600) is already where every other
|
||||||
|
token in this directory lives.
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import http.server
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
SYNC_DIR = os.environ.get("GITEA_SYNC_DIR", os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
ENV_PATH = os.path.join(SYNC_DIR, ".env")
|
||||||
|
PORT = int(os.environ.get("WEBHOOK_PORT", "3020"))
|
||||||
|
|
||||||
|
|
||||||
|
def _load_env_value(key):
|
||||||
|
try:
|
||||||
|
with open(ENV_PATH, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.split("#", 1)[0].strip()
|
||||||
|
if not line.startswith(key + "="):
|
||||||
|
continue
|
||||||
|
return line[len(key) + 1:].strip().strip("'").strip('"')
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
sys.stderr.write("%s - %s\n" % (self.address_string(), fmt % args))
|
||||||
|
|
||||||
|
def _reply(self, code, body=b""):
|
||||||
|
self.send_response(code)
|
||||||
|
self.end_headers()
|
||||||
|
if body:
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self._reply(200, b"gitea-github-webhook: listening\n")
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
secret = _load_env_value("WEBHOOK_SECRET").encode()
|
||||||
|
if not secret:
|
||||||
|
self._reply(503, b"WEBHOOK_SECRET not configured")
|
||||||
|
return
|
||||||
|
|
||||||
|
length = int(self.headers.get("Content-Length", 0) or 0)
|
||||||
|
body = self.rfile.read(length) if length else b""
|
||||||
|
|
||||||
|
sig = self.headers.get("X-Hub-Signature-256", "")
|
||||||
|
expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
|
||||||
|
if not sig or not hmac.compare_digest(sig, expected):
|
||||||
|
self._reply(401, b"bad signature")
|
||||||
|
return
|
||||||
|
|
||||||
|
event = self.headers.get("X-GitHub-Event", "")
|
||||||
|
if event == "ping":
|
||||||
|
self._reply(200, b"pong")
|
||||||
|
return
|
||||||
|
if event != "push":
|
||||||
|
self._reply(204)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.loads(body or b"{}")
|
||||||
|
full_name = payload["repository"]["full_name"]
|
||||||
|
except (json.JSONDecodeError, KeyError, TypeError):
|
||||||
|
self._reply(400, b"couldn't find repository.full_name in payload")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._reply(202, b"sync queued\n")
|
||||||
|
sync_script = os.path.join(SYNC_DIR, "gitea-github-sync.sh")
|
||||||
|
sync_env = dict(os.environ, SYNC_ENV=ENV_PATH)
|
||||||
|
subprocess.Popen(
|
||||||
|
["bash", sync_script, "--repo", full_name, "--pull-only"],
|
||||||
|
cwd=SYNC_DIR,
|
||||||
|
env=sync_env,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = http.server.ThreadingHTTPServer(("0.0.0.0", PORT), Handler)
|
||||||
|
server.serve_forever()
|
||||||
|
PYEOF
|
||||||
|
chmod +x "$DIR/gitea-github-webhook.py"
|
||||||
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$DIR/gitea-github-webhook.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
_gitea_write_webhook_service() {
|
||||||
|
local DIR="$1" RUN_USER="$2" RUN_HOME="$3" PORT="$4"
|
||||||
|
local _service="/etc/systemd/system/gitea-github-webhook.service"
|
||||||
|
|
||||||
|
cat > "$_service" << UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Gitea-GitHub Webhook Receiver (real-time mirror sync trigger)
|
||||||
|
After=network-online.target docker.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${RUN_USER}
|
||||||
|
Environment=HOME=${RUN_HOME}
|
||||||
|
Environment=GITEA_SYNC_DIR=${DIR}
|
||||||
|
Environment=WEBHOOK_PORT=${PORT}
|
||||||
|
ExecStart=/usr/bin/python3 ${DIR}/gitea-github-webhook.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now gitea-github-webhook.service
|
||||||
|
}
|
||||||
|
|
||||||
|
_gitea_remove_webhook_service() {
|
||||||
|
systemctl disable --now gitea-github-webhook.service 2>/dev/null || true
|
||||||
|
rm -f /etc/systemd/system/gitea-github-webhook.service
|
||||||
|
systemctl daemon-reload 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Offers the webhook receiver above as an addition to (not a replacement
|
||||||
|
# for) the timer set up in _gitea_run_sync_direction_step — the timer keeps
|
||||||
|
# covering the Gitea -> GitHub direction (and acts as a safety net for any
|
||||||
|
# push GitHub's webhook delivery ever misses), the webhook just gets the
|
||||||
|
# GitHub -> Gitea direction down from "up to one interval late" to seconds.
|
||||||
|
# Always asked on every install/reconfigure, same "live-editable extra"
|
||||||
|
# pattern as the direction+autosync step itself — see that function's own
|
||||||
|
# comment. Skipped (and any existing webhook torn down) outright when the
|
||||||
|
# chosen direction is push-only, since GitHub has nothing to notify about
|
||||||
|
# in that direction.
|
||||||
|
_gitea_offer_realtime_webhook() {
|
||||||
|
local DIR="$1" SYNC_FLAG="$2"
|
||||||
|
|
||||||
|
if [[ "$SYNC_FLAG" == "--push-only" ]]; then
|
||||||
|
_gitea_remove_webhook_service
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
local USE_WEBHOOK=""
|
||||||
|
prompt_yn " Also add a GitHub webhook for near-instant sync (push on GitHub -> synced here in seconds, instead of waiting for the timer above)? (y/n):" "n" USE_WEBHOOK
|
||||||
|
if [[ ! "$USE_WEBHOOK" =~ ^[Yy]$ ]]; then
|
||||||
|
_gitea_remove_webhook_service
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reuse an existing secret/port across reruns — rotating either one
|
||||||
|
# silently breaks a webhook GitHub already has configured against the
|
||||||
|
# old value, the same reasoning services/asterisk.sh's TURN port-range
|
||||||
|
# persistence follows for a live coturn install.
|
||||||
|
local WEBHOOK_SECRET WEBHOOK_PORT
|
||||||
|
WEBHOOK_SECRET="$(grep '^WEBHOOK_SECRET=' "$DIR/.env" 2>/dev/null | cut -d= -f2- | tr -d "'\"")"
|
||||||
|
WEBHOOK_PORT="$(grep '^WEBHOOK_PORT=' "$DIR/.env" 2>/dev/null | cut -d= -f2- | tr -d "'\"")"
|
||||||
|
[[ -z "$WEBHOOK_SECRET" ]] && WEBHOOK_SECRET="$(generate_password 40)"
|
||||||
|
if [[ -z "$WEBHOOK_PORT" ]]; then
|
||||||
|
WEBHOOK_PORT=3020
|
||||||
|
find_free_port WEBHOOK_PORT "$WEBHOOK_PORT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q '^WEBHOOK_SECRET=' "$DIR/.env" 2>/dev/null; then
|
||||||
|
sed -i "s|^WEBHOOK_SECRET=.*|WEBHOOK_SECRET='${WEBHOOK_SECRET}'|" "$DIR/.env"
|
||||||
|
else
|
||||||
|
echo "WEBHOOK_SECRET='${WEBHOOK_SECRET}'" >> "$DIR/.env"
|
||||||
|
fi
|
||||||
|
if grep -q '^WEBHOOK_PORT=' "$DIR/.env" 2>/dev/null; then
|
||||||
|
sed -i "s|^WEBHOOK_PORT=.*|WEBHOOK_PORT='${WEBHOOK_PORT}'|" "$DIR/.env"
|
||||||
|
else
|
||||||
|
echo "WEBHOOK_PORT='${WEBHOOK_PORT}'" >> "$DIR/.env"
|
||||||
|
fi
|
||||||
|
chmod 600 "$DIR/.env"
|
||||||
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$DIR/.env"
|
||||||
|
|
||||||
|
_gitea_write_webhook_receiver "$DIR"
|
||||||
|
_gitea_write_webhook_service "$DIR" "$ACTUAL_USER" "$ACTUAL_HOME" "$WEBHOOK_PORT"
|
||||||
|
log_success "Webhook receiver running on port ${WEBHOOK_PORT} (systemctl status gitea-github-webhook)."
|
||||||
|
|
||||||
|
# Bare port -> host.docker.internal:PORT, same convention as every other
|
||||||
|
# host-process (non-container) upstream in this repo — see the
|
||||||
|
# configure_caddy_for_service usage note in CLAUDE.md.
|
||||||
|
configure_caddy_for_service "Gitea GitHub Webhook" "$WEBHOOK_PORT" "gitea-webhook"
|
||||||
|
if [[ "$CADDY_SERVICE_CONFIGURED" == true ]]; then
|
||||||
|
if command -v ufw &>/dev/null; then
|
||||||
|
if [[ "$CADDY_SERVICE_MODE" == "local" ]]; then
|
||||||
|
ufw delete allow "${WEBHOOK_PORT}/tcp" 2>/dev/null || true
|
||||||
|
ufw_allow_from_caddy_net "${WEBHOOK_PORT}"
|
||||||
|
else
|
||||||
|
ufw allow "${WEBHOOK_PORT}/tcp" comment "Gitea GitHub webhook" >/dev/null 2>&1 || true
|
||||||
|
ensure_ufw_enabled
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
log_success "Now add the webhook on GitHub, for every repo you want instant sync from:"
|
||||||
|
log_info " Repo -> Settings -> Webhooks -> Add webhook"
|
||||||
|
log_info " Payload URL: https://${CADDY_SERVICE_DOMAIN}/"
|
||||||
|
log_info " Content type: application/json"
|
||||||
|
log_info " Secret: ${WEBHOOK_SECRET}"
|
||||||
|
log_info " Events: Just the push event"
|
||||||
|
log_info "The timer above still covers every other repo, and this one too, on its"
|
||||||
|
log_info "own schedule — the webhook is an addition, not a replacement for it."
|
||||||
|
else
|
||||||
|
log_warning "Webhook receiver is running (0.0.0.0:${WEBHOOK_PORT}) but nothing is exposing"
|
||||||
|
log_warning "it to the internet, so GitHub can't reach it yet — re-run this installer and"
|
||||||
|
log_warning "configure Caddy for it, or point your own reverse proxy at"
|
||||||
|
log_warning "127.0.0.1:${WEBHOOK_PORT} (or the container-reachable host IP) by hand."
|
||||||
|
log_info " Secret (for whenever you do expose it): ${WEBHOOK_SECRET}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
install_gitea() {
|
install_gitea() {
|
||||||
log_info "Setting up self-hosted Gitea..."
|
log_info "Setting up self-hosted Gitea..."
|
||||||
|
|
||||||
@@ -537,6 +787,8 @@ install_gitea() {
|
|||||||
echo "[DRY-RUN] Would ask sync direction (GitHub->Gitea / Gitea->GitHub / both) and whether"
|
echo "[DRY-RUN] Would ask sync direction (GitHub->Gitea / Gitea->GitHub / both) and whether"
|
||||||
echo "[DRY-RUN] to install a systemd timer for automatic sync, or print manual instructions"
|
echo "[DRY-RUN] to install a systemd timer for automatic sync, or print manual instructions"
|
||||||
echo "[DRY-RUN] Would offer to run a sync now (dry-run preview or for real), off-schedule"
|
echo "[DRY-RUN] Would offer to run a sync now (dry-run preview or for real), off-schedule"
|
||||||
|
echo "[DRY-RUN] Would offer a GitHub webhook receiver for near-instant GitHub->Gitea sync"
|
||||||
|
echo "[DRY-RUN] (systemd service + Caddy front door), unless direction is push-only"
|
||||||
echo "[DRY-RUN] Would offer \"Sign in with Authelia\" (OIDC) if Authelia is installed"
|
echo "[DRY-RUN] Would offer \"Sign in with Authelia\" (OIDC) if Authelia is installed"
|
||||||
echo "[DRY-RUN] Would offer zero-click Authelia login (reverse-proxy auth) if Authelia"
|
echo "[DRY-RUN] Would offer zero-click Authelia login (reverse-proxy auth) if Authelia"
|
||||||
echo "[DRY-RUN] and local Caddy are both installed — rewires Gitea onto caddy_net"
|
echo "[DRY-RUN] and local Caddy are both installed — rewires Gitea onto caddy_net"
|
||||||
@@ -567,6 +819,7 @@ install_gitea() {
|
|||||||
&& log_success "Gitea refreshed and restarted." \
|
&& log_success "Gitea refreshed and restarted." \
|
||||||
|| log_warning "Restart failed — check: docker compose -f $DIR/docker-compose.yml logs"
|
|| log_warning "Restart failed — check: docker compose -f $DIR/docker-compose.yml logs"
|
||||||
_gitea_run_sync_direction_step "$DIR"
|
_gitea_run_sync_direction_step "$DIR"
|
||||||
|
_gitea_offer_realtime_webhook "$DIR" "$_GITEA_SYNC_FLAG"
|
||||||
_gitea_offer_authelia_sso "$DIR"
|
_gitea_offer_authelia_sso "$DIR"
|
||||||
_gitea_offer_reverse_proxy_auth "$DIR"
|
_gitea_offer_reverse_proxy_auth "$DIR"
|
||||||
_gitea_offer_actions_runner "$DIR"
|
_gitea_offer_actions_runner "$DIR"
|
||||||
@@ -732,6 +985,7 @@ ENV
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
_gitea_run_sync_direction_step "$DIR"
|
_gitea_run_sync_direction_step "$DIR"
|
||||||
|
_gitea_offer_realtime_webhook "$DIR" "$_GITEA_SYNC_FLAG"
|
||||||
|
|
||||||
# ── Caddy — no forward_auth gate here. Gitea has its own built-in login,
|
# ── Caddy — no forward_auth gate here. Gitea has its own built-in login,
|
||||||
# unlike the no-auth-at-all apps elsewhere in this repo that need Caddy
|
# unlike the no-auth-at-all apps elsewhere in this repo that need Caddy
|
||||||
@@ -794,6 +1048,69 @@ Config (which repos, private/forks handling) lives at
|
|||||||
\`~/.config/gitea-github-sync/config\` — edit directly, or re-run
|
\`~/.config/gitea-github-sync/config\` — edit directly, or re-run
|
||||||
\`bash gitea-github-sync.sh --init\` to redo it interactively.
|
\`bash gitea-github-sync.sh --init\` to redo it interactively.
|
||||||
|
|
||||||
|
## Real-time sync via GitHub webhook (optional)
|
||||||
|
|
||||||
|
The setup above only covers the GitHub -> Gitea direction; it doesn't apply
|
||||||
|
if you chose Gitea -> GitHub only (GitHub has nothing to notify about in
|
||||||
|
that direction). Adds a small Python HTTP server
|
||||||
|
(\`gitea-github-webhook.py\`, in this directory) run as its own systemd
|
||||||
|
service (\`gitea-github-webhook.service\`) that GitHub POSTs to the instant
|
||||||
|
someone pushes — it verifies the request's HMAC signature against
|
||||||
|
\`WEBHOOK_SECRET\` in \`.env\`, then runs \`gitea-github-sync.sh --repo
|
||||||
|
owner/name --pull-only\` for just that one repo. The scheduled timer above
|
||||||
|
still runs on its own interval regardless — the webhook is an addition
|
||||||
|
that gets the GitHub -> Gitea direction down to seconds, not a replacement
|
||||||
|
for it (and still catches anything a missed webhook delivery would have
|
||||||
|
picked up next interval anyway).
|
||||||
|
|
||||||
|
Not set up yet, or want to change the port/secret? Re-run
|
||||||
|
\`sudo ./setup.sh gitea\` (Update mode is fine) and answer yes to "Also add
|
||||||
|
a GitHub webhook...". That only stands up the *receiver* on this box — you
|
||||||
|
still add the actual webhook on GitHub's side afterward, using the payload
|
||||||
|
URL and secret the installer printed (also readable back from \`.env\` as
|
||||||
|
\`WEBHOOK_PORT\` / \`WEBHOOK_SECRET\` if you need them again).
|
||||||
|
|
||||||
|
**Option A — one repo at a time.** Fastest, but only covers repos you do
|
||||||
|
this for individually:
|
||||||
|
repo -> Settings -> Webhooks -> Add webhook
|
||||||
|
- Payload URL: the URL the installer printed
|
||||||
|
- Content type: \`application/json\`
|
||||||
|
- Secret: your \`WEBHOOK_SECRET\`
|
||||||
|
- Events: "Just the push event"
|
||||||
|
|
||||||
|
**Option B — every repo on your account, current AND future, from one
|
||||||
|
setup.** A plain repo webhook (Option A) is always per-repo, no way around
|
||||||
|
that — but a personal GitHub App installed with "All repositories" access
|
||||||
|
covers every repo automatically, including ones you create afterward. No
|
||||||
|
receiver/code change needed for this: an App's webhook uses the exact same
|
||||||
|
HMAC-secret mechanism as a repo webhook, so the same \`WEBHOOK_SECRET\`
|
||||||
|
works for both.
|
||||||
|
|
||||||
|
1. GitHub -> Settings -> Developer settings -> GitHub Apps -> New GitHub App
|
||||||
|
2. Webhook URL: same payload URL as Option A. Webhook secret: your
|
||||||
|
\`WEBHOOK_SECRET\`. (Homepage URL is a separate, purely cosmetic field —
|
||||||
|
point it at anything, e.g. your GitHub profile; GitHub never sends
|
||||||
|
anything there, unlike Webhook URL.)
|
||||||
|
3. Permissions -> Repository permissions -> Contents: Read-only (required
|
||||||
|
to unlock the Push event checkbox)
|
||||||
|
4. Subscribe to events: Push only
|
||||||
|
5. Where can this GitHub App be installed: "Only on this account"
|
||||||
|
6. Create it, then Install App -> choose "All repositories" -> Install
|
||||||
|
|
||||||
|
If you'd already added Option A webhooks on a few repos, they're now
|
||||||
|
redundant (not harmful, just two triggers per push) — remove them once
|
||||||
|
the App is confirmed working.
|
||||||
|
|
||||||
|
**Verify either option** — push to a repo, then watch it arrive:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
systemctl status gitea-github-webhook # is it running?
|
||||||
|
journalctl -u gitea-github-webhook -f # watch it receive + trigger syncs
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
GitHub also shows delivery attempts and response codes: repo (or App) ->
|
||||||
|
Settings -> Webhooks -> the webhook -> Recent Deliveries.
|
||||||
|
|
||||||
## Sign in with Authelia (optional)
|
## Sign in with Authelia (optional)
|
||||||
|
|
||||||
If Authelia is installed, re-run \`sudo ./setup.sh gitea\` (Update mode is
|
If Authelia is installed, re-run \`sudo ./setup.sh gitea\` (Update mode is
|
||||||
|
|||||||
+2781
-168
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user