Z User commited on
Commit
a9f57ed
·
1 Parent(s): 31a92da

feat: persist WeChat credentials via HF Space Secrets + account JSON

Browse files

Three-layer persistence ensures WeChat NEVER needs re-scanning:
1. HF Space Secrets (WEIXIN_TOKEN, WEIXIN_ACCOUNT_ID) — survives everything
2. .env file — gateway's load_env picks these up
3. /data/hermes/weixin/accounts/<id>.json — gateway's load_weixin_account()

On every startup, start.sh:
- Reads WEIXIN_TOKEN/ACCOUNT_ID from HF Secrets (env vars)
- Writes them to both .env and account JSON
- Falls back to reading from persisted account JSON if secrets missing
- Logs masked values (never full tokens in build logs)

Files changed (1) hide show
  1. start.sh +47 -30
start.sh CHANGED
@@ -33,42 +33,48 @@ if [ ! -L "$WEIXIN_DIR" ]; then
33
  echo "Symlink: weixin -> /data/hermes/weixin"
34
  fi
35
 
36
- # Auto-discover WeChat account_id AND token from persisted session files
37
- # The QR login flow saves credentials to ~/.hermes/weixin/accounts/<account_id>.json
38
- # We export both WEIXIN_ACCOUNT_ID and WEIXIN_TOKEN so the gateway picks them up
39
  ACCOUNTS_DIR="/data/hermes/weixin/accounts"
40
- if [ -z "$WEIXIN_ACCOUNT_ID" ] && [ -d "$ACCOUNTS_DIR" ]; then
41
- LATEST=$(find "$ACCOUNTS_DIR" -name "*.json" ! -name "*.context-tokens.json" ! -name "*.sync.json" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | awk '{print $2}')
42
- if [ -n "$LATEST" ]; then
43
- DISCOVERED_ID=$(basename "$LATEST" .json)
44
- export WEIXIN_ACCOUNT_ID="$DISCOVERED_ID"
45
- echo "Auto-discovered WEIXIN_ACCOUNT_ID=$DISCOVERED_ID"
46
- else
47
- echo "No WeChat account files found in $ACCOUNTS_DIR"
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  fi
49
  fi
50
 
51
- # Also restore WEIXIN_TOKEN from persisted account file if not already set
52
- # This is needed because .env generated from HF Space Secrets doesn't contain WEIXIN_TOKEN
53
- if [ -z "$WEIXIN_TOKEN" ] && [ -n "$WEIXIN_ACCOUNT_ID" ]; then
54
  ACCOUNT_FILE="$ACCOUNTS_DIR/${WEIXIN_ACCOUNT_ID}.json"
55
- if [ -f "$ACCOUNT_FILE" ]; then
56
- DISCOVERED_TOKEN=$(python3 -c "
57
- import json, sys
58
- try:
59
- d = json.load(open('$ACCOUNT_FILE'))
60
- print(d.get('token', ''))
61
- except: print('')
62
- " 2>/dev/null)
63
- if [ -n "$DISCOVERED_TOKEN" ]; then
64
- export WEIXIN_TOKEN="$DISCOVERED_TOKEN"
65
- echo "Restored WEIXIN_TOKEN from persisted account file"
66
- else
67
- echo "WARNING: WeChat account file found but token is empty — QR re-scan needed"
68
- fi
69
- else
70
- echo "WARNING: WeChat account file $ACCOUNT_FILE not found — QR scan needed"
71
  fi
 
 
72
  fi
73
 
74
  # -- Persist .env across container rebuilds --
@@ -98,6 +104,8 @@ if [ ! -f "$ENV_DATA" ] && [ -n "$OPENROUTER_API_KEY" ]; then
98
  echo "HERMES_ACCEPT_HOOKS=1"
99
  [ -n "$MEMPALACE_PALACE_PATH" ] && echo "MEMPALACE_PALACE_PATH=$MEMPALACE_PALACE_PATH"
100
  [ -n "$FIRECRAWL_API_KEY" ] && echo "FIRECRAWL_API_KEY=$FIRECRAWL_API_KEY"
 
 
101
  } > "$ENV_DATA"
102
  chmod 600 "$ENV_DATA"
103
  echo "Created .env from Space Secrets (keys masked below)"
@@ -106,6 +114,7 @@ if [ ! -f "$ENV_DATA" ] && [ -n "$OPENROUTER_API_KEY" ]; then
106
  [ -n "$FEISHU_APP_ID" ] && echo " FEISHU_APP_ID=$FEISHU_APP_ID"
107
  [ -n "$FEISHU_APP_SECRET" ] && echo " FEISHU_APP_SECRET=$(_mask_val "$FEISHU_APP_SECRET")"
108
  [ -n "$FIRECRAWL_API_KEY" ] && echo " FIRECRAWL_API_KEY=$(_mask_val "$FIRECRAWL_API_KEY")"
 
109
  fi
110
 
111
  # Fallback: if no secrets and no persistent data
@@ -123,6 +132,14 @@ else
123
  echo "Symlink exists: .env"
124
  fi
125
 
 
 
 
 
 
 
 
 
126
  # ── Persist config.yaml across container rebuilds ──
127
  # WebUI settings page and WeChat save flow update ~/.hermes/config.yaml at runtime
128
  CFG_FILE="$HERMES_HOME/config.yaml"
 
33
  echo "Symlink: weixin -> /data/hermes/weixin"
34
  fi
35
 
36
+ # ── WeChat credential persistence ──
37
+ # Priority: HF Space Secrets > persisted account JSON file > .env file
38
+ # Once set via HF Space Secrets, WeChat survives ALL container rebuilds.
39
  ACCOUNTS_DIR="/data/hermes/weixin/accounts"
40
+ mkdir -p "$ACCOUNTS_DIR"
41
+
42
+ if [ -z "$WEIXIN_ACCOUNT_ID" ] || [ -z "$WEIXIN_TOKEN" ]; then
43
+ # Fallback: restore from persisted account JSON file
44
+ if [ -z "$WEIXIN_ACCOUNT_ID" ] && [ -d "$ACCOUNTS_DIR" ]; then
45
+ LATEST=$(find "$ACCOUNTS_DIR" -name "*.json" ! -name "*.context-tokens.json" ! -name "*.sync.json" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | awk '{print $2}')
46
+ if [ -n "$LATEST" ]; then
47
+ DISCOVERED_ID=$(basename "$LATEST" .json)
48
+ export WEIXIN_ACCOUNT_ID="$DISCOVERED_ID"
49
+ echo "Auto-discovered WEIXIN_ACCOUNT_ID=$DISCOVERED_ID"
50
+ fi
51
+ fi
52
+ if [ -z "$WEIXIN_TOKEN" ] && [ -n "$WEIXIN_ACCOUNT_ID" ]; then
53
+ ACCOUNT_FILE="$ACCOUNTS_DIR/${WEIXIN_ACCOUNT_ID}.json"
54
+ if [ -f "$ACCOUNT_FILE" ]; then
55
+ DISCOVERED_TOKEN=$(python3 -c "import json; d=json.load(open('$ACCOUNT_FILE')); print(d.get('token',''))" 2>/dev/null)
56
+ if [ -n "$DISCOVERED_TOKEN" ]; then
57
+ export WEIXIN_TOKEN="$DISCOVERED_TOKEN"
58
+ echo "Restored WEIXIN_TOKEN from persisted account file"
59
+ fi
60
+ fi
61
  fi
62
  fi
63
 
64
+ if [ -n "$WEIXIN_ACCOUNT_ID" ] && [ -n "$WEIXIN_TOKEN" ]; then
65
+ echo "WeChat credentials ready (account=$(_mask_val "$WEIXIN_ACCOUNT_ID"))"
66
+ # Persist credentials to account JSON so gateway's load_weixin_account() also finds them
67
  ACCOUNT_FILE="$ACCOUNTS_DIR/${WEIXIN_ACCOUNT_ID}.json"
68
+ if [ ! -f "$ACCOUNT_FILE" ] || ! python3 -c "import json; d=json.load(open('$ACCOUNT_FILE')); exit(0 if d.get('token') else 1)" 2>/dev/null; then
69
+ python3 -c "
70
+ import json, time
71
+ payload = {'token': '$WEIXIN_TOKEN', 'base_url': 'https://ilinkai.weixin.qq.com', 'saved_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}
72
+ with open('$ACCOUNT_FILE', 'w') as f: json.dump(payload, f)
73
+ " 2>/dev/null && chmod 600 "$ACCOUNT_FILE"
74
+ echo "WeChat credentials persisted to account file"
 
 
 
 
 
 
 
 
 
75
  fi
76
+ else
77
+ echo "WARNING: WeChat not configured (no token/account). Run 'hermes gateway setup' to scan QR."
78
  fi
79
 
80
  # -- Persist .env across container rebuilds --
 
104
  echo "HERMES_ACCEPT_HOOKS=1"
105
  [ -n "$MEMPALACE_PALACE_PATH" ] && echo "MEMPALACE_PALACE_PATH=$MEMPALACE_PALACE_PATH"
106
  [ -n "$FIRECRAWL_API_KEY" ] && echo "FIRECRAWL_API_KEY=$FIRECRAWL_API_KEY"
107
+ [ -n "$WEIXIN_ACCOUNT_ID" ] && echo "WEIXIN_ACCOUNT_ID=$WEIXIN_ACCOUNT_ID"
108
+ [ -n "$WEIXIN_TOKEN" ] && echo "WEIXIN_TOKEN=$WEIXIN_TOKEN"
109
  } > "$ENV_DATA"
110
  chmod 600 "$ENV_DATA"
111
  echo "Created .env from Space Secrets (keys masked below)"
 
114
  [ -n "$FEISHU_APP_ID" ] && echo " FEISHU_APP_ID=$FEISHU_APP_ID"
115
  [ -n "$FEISHU_APP_SECRET" ] && echo " FEISHU_APP_SECRET=$(_mask_val "$FEISHU_APP_SECRET")"
116
  [ -n "$FIRECRAWL_API_KEY" ] && echo " FIRECRAWL_API_KEY=$(_mask_val "$FIRECRAWL_API_KEY")"
117
+ [ -n "$WEIXIN_TOKEN" ] && echo " WEIXIN_TOKEN=$(_mask_val "$WEIXIN_TOKEN")"
118
  fi
119
 
120
  # Fallback: if no secrets and no persistent data
 
132
  echo "Symlink exists: .env"
133
  fi
134
 
135
+ # Ensure WEIXIN_TOKEN/ACCOUNT_ID are in .env even if file was created earlier without them
136
+ if [ -f "$ENV_DATA" ] && [ -n "$WEIXIN_TOKEN" ] && ! grep -q '^WEIXIN_TOKEN=' "$ENV_DATA" 2>/dev/null; then
137
+ echo "WEIXIN_TOKEN=$WEIXIN_TOKEN" >> "$ENV_DATA"
138
+ fi
139
+ if [ -f "$ENV_DATA" ] && [ -n "$WEIXIN_ACCOUNT_ID" ] && ! grep -q '^WEIXIN_ACCOUNT_ID=' "$ENV_DATA" 2>/dev/null; then
140
+ echo "WEIXIN_ACCOUNT_ID=$WEIXIN_ACCOUNT_ID" >> "$ENV_DATA"
141
+ fi
142
+
143
  # ── Persist config.yaml across container rebuilds ──
144
  # WebUI settings page and WeChat save flow update ~/.hermes/config.yaml at runtime
145
  CFG_FILE="$HERMES_HOME/config.yaml"