Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/sync-upstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Sync Upstream and Release

on:
schedule:
# Check for upstream changes daily at 00:00 UTC
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
force_release:
description: 'Force a release even if no upstream changes'
required: false
default: 'false'
type: choice
options:
- 'false'
- 'true'

env:
UPSTREAM_REPO: https://github.com/router-for-me/EasyCLI.git
UPSTREAM_BRANCH: main

jobs:
sync:
name: Sync Upstream
runs-on: ubuntu-latest
steps:
Comment on lines +18 to +26
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow pushes commits and tags back to the repository using GITHUB_TOKEN, but it doesn't declare explicit token permissions. To make scheduled/manual runs reliable across org/repo settings, add permissions: contents: write (and any other required scopes) at the workflow or job level.

Copilot uses AI. Check for mistakes.
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Add upstream remote
run: |
git remote add upstream ${{ env.UPSTREAM_REPO }} || true
git fetch upstream ${{ env.UPSTREAM_BRANCH }}

- name: Check for upstream changes
id: check
run: |
LOCAL_SHA=$(git rev-parse HEAD)
UPSTREAM_SHA=$(git rev-parse upstream/${{ env.UPSTREAM_BRANCH }})

if [ "$LOCAL_SHA" = "$UPSTREAM_SHA" ] && [ "${{ github.event.inputs.force_release }}" != "true" ]; then
echo "No upstream changes detected."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "Upstream changes detected or force release requested."
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
Comment on lines +46 to +55
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upstream-change detection compares HEAD vs upstream/main SHA equality. If this repo intentionally carries additional local commits on top of upstream (common for forks), the SHAs will always differ even when upstream has no new commits, causing unnecessary merges/tags. Consider checking whether upstream is ahead of local instead (e.g., count commits in HEAD..upstream/main) and only sync when that count is non-zero (unless force_release is true).

Copilot uses AI. Check for mistakes.

- name: Merge upstream changes
if: steps.check.outputs.has_changes == 'true'
run: |
git merge upstream/${{ env.UPSTREAM_BRANCH }} --no-edit --allow-unrelated-histories || {
echo "::warning::Merge conflict detected. Resolving by keeping our changes."
git checkout --ours .
git add .
git commit -m "Merge upstream (conflicts resolved by keeping local)"
Comment on lines +61 to +64
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-resolving merge conflicts with git checkout --ours . silently discards upstream changes in conflicting files, which defeats the purpose of syncing (and can miss important fixes). A safer approach is to fail the job on conflicts and either open an issue/PR for manual resolution or push the conflicted merge to a separate branch for review.

Suggested change
echo "::warning::Merge conflict detected. Resolving by keeping our changes."
git checkout --ours .
git add .
git commit -m "Merge upstream (conflicts resolved by keeping local)"
echo "::error::Merge conflict detected while syncing with upstream. Please resolve manually."
exit 1

Copilot uses AI. Check for mistakes.
}

- name: Push merged changes
if: steps.check.outputs.has_changes == 'true'
run: |
git push origin HEAD

Comment on lines +67 to +71
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing merged changes directly to origin HEAD on a schedule can fail on protected default branches and makes it easy to publish unreviewed upstream merges. Consider pushing the merge result to a dedicated bot branch and opening/updating a PR instead, then tagging only after that PR is merged.

Copilot uses AI. Check for mistakes.
- name: Create release tag (triggers build workflow)
if: steps.check.outputs.has_changes == 'true'
run: |
DATE=$(date +%Y%m%d)
SHORT_SHA=$(git rev-parse --short HEAD)
TAG="v${DATE}-sync-${SHORT_SHA}"
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tag name v${DATE}-sync-${SHORT_SHA} can collide if the workflow is re-run for the same commit/day (e.g., manual re-run after a transient failure), causing git tag/git push to fail. Consider checking for an existing tag before creating it, or including ${{ github.run_id }}/${{ github.run_number }} in the tag to guarantee uniqueness.

Suggested change
TAG="v${DATE}-sync-${SHORT_SHA}"
TAG="v${DATE}-sync-${SHORT_SHA}-${GITHUB_RUN_ID}"

Copilot uses AI. Check for mistakes.
echo "Creating tag: ${TAG}"
git tag "$TAG"
git push origin "$TAG"
echo "Tag pushed — the 'Release on Tag' workflow will handle the build."
20 changes: 16 additions & 4 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1128,8 +1128,14 @@ fn start_cliproxyapi(app: tauri::AppHandle) -> Result<serde_json::Value, String>
eprintln!("[PORT_CLEANUP] Warning: {}", e);
}

// Generate random password for local mode
let password = generate_random_password();
// Reuse existing secret-key from config if present, otherwise generate a new one
let existing_key = conf
.get("remote-management")
.and_then(|v| v.get("secret-key"))
.and_then(|v| v.as_str())
.filter(|s| !s.trim().is_empty())
.map(|s| s.to_string());
let password = existing_key.unwrap_or_else(|| generate_random_password());

// Store the password for keep-alive authentication
*CLI_PROXY_PASSWORD.lock() = Some(password.clone());
Comment on lines 1139 to 1143
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

password can now be sourced from a persisted remote-management.secret-key, but the function later logs the full command-line args including --password (which will leak the management secret into logs/console). Please redact the password in logs (or remove it from the logged args) to avoid exposing a long-lived credential.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -1258,8 +1264,14 @@ fn restart_cliproxyapi(app: tauri::AppHandle) -> Result<(), String> {
eprintln!("[PORT_CLEANUP] Warning: {}", e);
}

// Generate random password for local mode
let password = generate_random_password();
// Reuse existing secret-key from config if present, otherwise generate a new one
let existing_key = conf
.get("remote-management")
.and_then(|v| v.get("secret-key"))
.and_then(|v| v.as_str())
.filter(|s| !s.trim().is_empty())
.map(|s| s.to_string());
let password = existing_key.unwrap_or_else(|| generate_random_password());

// Store the password for keep-alive authentication
*CLI_PROXY_PASSWORD.lock() = Some(password.clone());
Expand Down