Repository navigation
Expand file tree
/
Copy pathapp.py
More file actions
265 lines (229 loc) · 8.68 KB
/
Copy pathapp.py
File metadata and controls
265 lines (229 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/usr/bin/env python3
import argparse
import logging
import os
from git_config import resolve_relay_id, resolve_relay_url, resolve_webhook_url
from operations_queue import OperationsQueue
from persistence import PersistenceManager
from relay_auth import generate_setup, print_setup
from relay_client import RelayClient
from sync_engine import SyncEngine
from web_server import create_server
from webhook_handler import WebhookProcessor
from websocket_listener import WebsocketChangeListener
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
def startup_sync_all_folders(sync_engine, persistence_manager):
"""Run initial sync for all configured folders on startup"""
try:
print("Running startup sync...")
# Get all configured git connectors
git_connectors = persistence_manager.git_config.connectors
if not git_connectors:
print("No git connectors configured, skipping startup sync")
return
print(f"Found {len(git_connectors)} configured git connectors")
# Group connectors by relay_id to sync each relay
relays_to_sync = set(connector.relay_id for connector in git_connectors)
total_synced = 0
total_failed = 0
for relay_id in relays_to_sync:
try:
print(f"Syncing relay: {relay_id}")
# Run sync for all folders in this relay
results = sync_engine.sync_relay_all_folders(relay_id)
# Count results
for result in results:
if result.success:
total_synced += 1
if result.operations:
print(
f" ✓ Synced folder {result.folder_id} ({len(result.operations)} operations)"
)
else:
print(f" ✓ Folder {result.folder_id} up to date")
else:
total_failed += 1
print(f" ✗ Failed to sync folder {result.folder_id}: {result.error}")
except Exception as e:
logger.warning(f"Error syncing relay {relay_id} during startup: {e}")
total_failed += 1
# Commit any changes from startup sync
print("Committing startup sync changes...")
committed = persistence_manager.commit_changes()
if committed:
print("✓ Startup sync changes committed to git")
else:
print("No changes to commit from startup sync")
print(f"Startup sync complete: {total_synced} folders synced, {total_failed} failed")
except Exception as e:
logger.error(f"Error during startup sync: {e}")
print("Warning: Startup sync failed, continuing with server start...")
def run_server(
relay_server_url: str,
relay_server_api_key: str,
webhook_secret: str,
port=8000,
commit_interval=10,
data_dir=".",
git_config_file=None,
websocket_reconnect_delay=5.0,
relay_id: str = "",
):
if not relay_id:
raise ValueError("Relay ID is required")
if not relay_server_api_key:
print_setup(
generate_setup(
server_url=relay_server_url.rstrip("/"),
relay_id=relay_id,
expires_days=None,
webhook_url=resolve_webhook_url(
None,
data_dir=data_dir,
git_config_file=git_config_file,
),
)
)
return
print(f"Relay server: {relay_server_url}")
print(f"Data directory: {data_dir}")
print(f"Git commit interval set to {commit_interval} seconds")
websocket_listener = None
try:
# Initialize components
relay_client = RelayClient(relay_server_url, relay_server_api_key)
persistence_manager = PersistenceManager(data_dir, git_config_file)
sync_engine = SyncEngine(data_dir, relay_client, persistence_manager)
webhook_processor = WebhookProcessor(relay_client)
operations_queue = OperationsQueue(sync_engine, commit_interval)
web_server = create_server(
webhook_processor, operations_queue, webhook_secret, persistence_manager
)
websocket_listener = WebsocketChangeListener(
relay_client,
operations_queue,
persistence_manager,
reconnect_delay=websocket_reconnect_delay,
)
# Run startup sync for all configured git connectors
startup_sync_all_folders(sync_engine, persistence_manager)
websocket_listener.start()
# Start the server
web_server.run(port=port)
except KeyboardInterrupt:
print("\nServer shutting down...")
# Graceful shutdown would be handled by WebServer
except Exception as e:
logger.error(f"Error starting server: {e}")
raise
finally:
if websocket_listener is not None:
websocket_listener.stop()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Relay Git Sync app")
parser.add_argument("--port", type=int, default=8000, help="Port to listen on")
parser.add_argument(
"--commit-interval",
type=int,
default=10,
help="Git commit interval in seconds (default: 10)",
)
parser.add_argument(
"--relay-server-url",
help="Relay server URL (overrides RELAY_SERVER_URL and [relay].url)",
)
parser.add_argument(
"--relay-id",
help="Relay ID (overrides RELAY_ID and [relay].id)",
)
parser.add_argument(
"--data-dir",
default=os.getenv("RELAY_GIT_DATA_DIR", "."),
help="Directory for repo and persistent storage (default: from RELAY_GIT_DATA_DIR env var or current directory)",
)
parser.add_argument(
"--webhook-secret",
default=os.getenv("WEBHOOK_SECRET", ""),
help="Webhook secret for authentication (default: from WEBHOOK_SECRET env var)",
)
parser.add_argument(
"-c",
"--config",
dest="config",
default=None,
help="Path to git_connectors.toml (default: <data-dir>/git_connectors.toml)",
)
parser.add_argument(
"--git-config-file",
dest="config",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--websocket-reconnect-delay",
type=float,
default=float(os.getenv("RELAY_WEBSOCKET_RECONNECT_DELAY", "5")),
help="Seconds to wait before reconnecting Relay websocket listeners",
)
args = parser.parse_args()
args.relay_server_url = resolve_relay_url(
args.relay_server_url,
data_dir=args.data_dir,
git_config_file=args.config,
)
if not args.relay_server_url:
print(
"Error: Relay server URL is required. Set [relay].url in git_connectors.toml or pass --relay-server-url."
)
print("For management commands (setup, webhook, ssh, api), use: git-sync")
exit(1)
try:
args.relay_id = resolve_relay_id(
args.relay_id,
data_dir=args.data_dir,
git_config_file=args.config,
)
except ValueError as e:
print(f"Error: {e}")
exit(1)
if not args.relay_id:
print(
"Error: Relay ID is required. Set [relay].id in git_connectors.toml or pass --relay-id."
)
exit(1)
relay_server_api_key = os.getenv("RELAY_SERVER_API_KEY")
if not relay_server_api_key:
# Setup mode: print auth setup instructions and exit cleanly. A nonzero
# exit would crash-loop under restart policies, minting a fresh keypair
# on every restart.
run_server(
args.relay_server_url,
relay_server_api_key,
args.webhook_secret,
args.port,
args.commit_interval,
args.data_dir,
args.config,
args.websocket_reconnect_delay,
relay_id=args.relay_id,
)
print("Set RELAY_SERVER_API_KEY and restart to begin syncing.")
exit(0)
# Check for SSH key (warn if missing, don't fail)
if not os.getenv("SSH_PRIVATE_KEY"):
print(
"Warning: SSH_PRIVATE_KEY environment variable not set. Git push operations will fail."
)
print("To use Git remotes, set SSH_PRIVATE_KEY with your private key.")
run_server(
args.relay_server_url,
relay_server_api_key,
args.webhook_secret,
args.port,
args.commit_interval,
args.data_dir,
args.config,
args.websocket_reconnect_delay,
relay_id=args.relay_id,
)