Skip to content

Commit 863494d

Browse files
ADFA-3269 Share nightly release build with private Telegram channel (#1073)
Upload nightly release to Cloudflare R2 and announce to early access telegram channel; remove obsolete Greengeeks ssh setup
1 parent 4755d0b commit 863494d

2 files changed

Lines changed: 108 additions & 52 deletions

File tree

.github/workflows/release.yml

Lines changed: 29 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -582,60 +582,16 @@ jobs:
582582
583583
echo "FIREBASE_CONSOLE_URL=$FIREBASE_URL" >> $GITHUB_OUTPUT
584584
585-
- name: Set up SSH key
585+
- name: Install uv
586+
uses: astral-sh/setup-uv@v6
587+
588+
- name: Upload APK to Cloudflare R2
586589
env:
587-
GREENGEEKS_HOST: ${{ vars.GREENGEEKS_SSH_HOST }}
588-
GREENGEEKS_KEY: ${{ secrets.GREENGEEKS_SSH_PRIVATE_KEY }}
589-
GREENGEEKS_USER: ${{ vars.GREENGEEKS_SSH_USER }}
590+
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
591+
CLOUDFLARE_KEY_ID: ${{ vars.CLOUDFLARE_KEY_ID }}
592+
CLOUDFLARE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }}
590593
run: |
591-
mkdir -p ~/.ssh
592-
if [ -z "$GREENGEEKS_HOST" ]; then
593-
echo "Error: SSH_HOST variable is not set"
594-
exit 1
595-
fi
596-
# Write the SSH key, ensuring proper formatting
597-
echo "$GREENGEEKS_KEY" > ~/.ssh/id_rsa
598-
# Remove any trailing newlines and ensure proper key format
599-
sed -i '' -e '$ { /^$/ d; }' ~/.ssh/id_rsa 2>/dev/null || sed -i '$ { /^$/ d; }' ~/.ssh/id_rsa
600-
chmod 600 ~/.ssh/id_rsa
601-
# Verify key format
602-
if ! grep -q "BEGIN.*PRIVATE KEY" ~/.ssh/id_rsa; then
603-
echo "Error: SSH key does not appear to be in correct format"
604-
exit 1
605-
fi
606-
# Configure SSH to use only the key file and disable other auth methods
607-
cat > ~/.ssh/config <<EOF
608-
Host *
609-
IdentitiesOnly yes
610-
PreferredAuthentications publickey
611-
StrictHostKeyChecking no
612-
UserKnownHostsFile ~/.ssh/known_hosts
613-
PubkeyAuthentication yes
614-
PasswordAuthentication no
615-
ChallengeResponseAuthentication no
616-
GSSAPIAuthentication no
617-
GSSAPIKeyExchange no
618-
GSSAPIDelegateCredentials no
619-
Host $GREENGEEKS_HOST
620-
User $GREENGEEKS_USER
621-
IdentityFile ~/.ssh/id_rsa
622-
IdentitiesOnly yes
623-
PreferredAuthentications publickey
624-
PubkeyAuthentication yes
625-
PasswordAuthentication no
626-
ChallengeResponseAuthentication no
627-
GSSAPIAuthentication no
628-
GSSAPIKeyExchange no
629-
GSSAPIDelegateCredentials no
630-
NumberOfPasswordPrompts 0
631-
EOF
632-
chmod 600 ~/.ssh/config
633-
# Disable SSH agent completely
634-
unset SSH_AUTH_SOCK
635-
unset SSH_AGENT_PID
636-
# Remove any default SSH keys that might interfere
637-
rm -f ~/.ssh/id_ed25519 ~/.ssh/id_ecdsa ~/.ssh/id_dsa ~/.ssh/id_rsa.pub 2>/dev/null
638-
ssh-keyscan -H "$GREENGEEKS_HOST" >> ~/.ssh/known_hosts 2>/dev/null
594+
uv run --with boto3 scripts/cloudflare-r2-upload.py "${{ steps.find_apk.outputs.APK_PATH }}" "${{ matrix.variant }}"
639595
640596
- name: Clean up build folder after upload
641597
run: |
@@ -735,6 +691,27 @@ jobs:
735691
736692
rm -f payload.json
737693
694+
- name: Send Telegram message
695+
env:
696+
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
697+
TELEGRAM_EARLY_ACCESS_CHAT_ID: ${{ vars.TELEGRAM_EARLY_ACCESS_CHAT_ID }}
698+
APK_PATH: ${{ steps.find_apk.outputs.APK_PATH }}
699+
VARIANT: ${{ matrix.variant }}
700+
run: |
701+
GIT_LOG=$(git log --oneline --since "24 hours ago" || true)
702+
if [ -z "$GIT_LOG" ]; then
703+
GIT_LOG="(no commits in the last 24 hours)"
704+
fi
705+
APK_BASENAME=$(basename "$APK_PATH")
706+
APK_FILENAME="${APK_BASENAME%.*}-${VARIANT}.${APK_BASENAME##*.}"
707+
DOWNLOAD_URL="https://download.appdevforall.org/${APK_FILENAME}"
708+
MESSAGE="${GIT_LOG}"$'\n\n'"Download: ${DOWNLOAD_URL}"
709+
# Telegram message limit 4096; use first 4096 chars
710+
MESSAGE="${MESSAGE:0:4096}"
711+
curl -s -X POST -H "Content-Type: application/json" \
712+
-d "$(jq -n --arg chat_id "$TELEGRAM_EARLY_ACCESS_CHAT_ID" --arg text "$MESSAGE" '{chat_id: $chat_id, text: $text}')" \
713+
"https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage"
714+
738715
- name: Cleanup google-services.json
739716
if: always()
740717
run: |

scripts/cloudflare-r2-upload.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
"""Upload a file (e.g. APK) to Cloudflare R2. Credentials via env: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_KEY_ID, CLOUDFLARE_SECRET_ACCESS_KEY."""
3+
import sys
4+
import os
5+
import boto3
6+
from botocore.config import Config
7+
8+
REQUIRED_ENV = (
9+
"CLOUDFLARE_ACCOUNT_ID",
10+
"CLOUDFLARE_KEY_ID",
11+
"CLOUDFLARE_SECRET_ACCESS_KEY",
12+
)
13+
14+
for name in REQUIRED_ENV:
15+
if not os.environ.get(name):
16+
print(f"ERROR: {name} environment variable is not set.", file=sys.stderr)
17+
sys.exit(1)
18+
19+
CLOUDFLARE_ACCOUNT_ID = os.environ["CLOUDFLARE_ACCOUNT_ID"]
20+
CLOUDFLARE_KEY_ID = os.environ["CLOUDFLARE_KEY_ID"]
21+
CLOUDFLARE_SECRET_ACCESS_KEY = os.environ["CLOUDFLARE_SECRET_ACCESS_KEY"]
22+
BUCKET_NAME = "apk-repo"
23+
24+
R2_ENDPOINT_URL = f"https://{CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com"
25+
26+
config = Config(
27+
read_timeout=300,
28+
connect_timeout=60,
29+
retries={"max_attempts": 10},
30+
)
31+
32+
s3 = boto3.client(
33+
service_name="s3",
34+
endpoint_url=R2_ENDPOINT_URL,
35+
aws_access_key_id=CLOUDFLARE_KEY_ID,
36+
aws_secret_access_key=CLOUDFLARE_SECRET_ACCESS_KEY,
37+
region_name="auto",
38+
config=config,
39+
)
40+
41+
if len(sys.argv) < 3:
42+
print("Usage: cloudflare-r2-upload.py <file_path> <variant>", file=sys.stderr)
43+
sys.exit(1)
44+
45+
file_path = sys.argv[1]
46+
variant = sys.argv[2]
47+
file_size = os.path.getsize(file_path)
48+
base_name = os.path.basename(file_path)
49+
# Inject variant into filename before .apk so v7 and v8 upload to distinct R2 keys
50+
name_root, ext = os.path.splitext(base_name)
51+
file_name = f"{name_root}-{variant}{ext}"
52+
53+
extra_args = {}
54+
if file_name.lower().endswith(".apk"):
55+
extra_args["ContentType"] = "application/vnd.android.package-archive"
56+
57+
# Progress callback: print new lines at 10% intervals for CI-friendly logs (bytes_amount is incremental per call)
58+
_seen_so_far = [0]
59+
_last_printed_pct = [-1]
60+
61+
def progress_callback(bytes_amount):
62+
_seen_so_far[0] += bytes_amount
63+
if file_size <= 0:
64+
return
65+
pct = int(100 * _seen_so_far[0] / file_size)
66+
if pct >= _last_printed_pct[0] + 10 or pct == 100:
67+
_last_printed_pct[0] = pct
68+
mb = _seen_so_far[0] / (1024 * 1024)
69+
total_mb = file_size / (1024 * 1024)
70+
print(f"Upload progress: {pct}% ({mb:.1f} MB / {total_mb:.1f} MB)", flush=True)
71+
72+
73+
upload_kwargs = {"Callback": progress_callback}
74+
if extra_args:
75+
upload_kwargs["ExtraArgs"] = extra_args
76+
77+
print(f"Uploading {file_name} ({file_size / (1024*1024):.1f} MB) to R2...", flush=True)
78+
s3.upload_file(file_path, BUCKET_NAME, file_name, **upload_kwargs)
79+
print("Upload complete.", flush=True)

0 commit comments

Comments
 (0)