kacapower commited on
Commit
14d4369
·
verified ·
1 Parent(s): 5ca4e47

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +12 -11
  2. start.sh +7 -0
  3. sync.py +99 -0
Dockerfile CHANGED
@@ -1,31 +1,32 @@
1
  FROM node:20-bookworm-slim
2
 
3
- # System dependencies
4
  RUN apt-get update && apt-get install -y --no-install-recommends \
5
  git \
6
  ca-certificates \
7
  curl \
 
 
8
  && rm -rf /var/lib/apt/lists/*
9
 
10
- # Install OpenCode globally via NPM to bypass the bash script rate-limit error.
11
  RUN npm install -g opencode-ai
12
 
13
- # The node base image ALREADY has a user with UID 1000 named "node".
14
- # We will use this existing user for Hugging Face Spaces.
15
- ENV HOME=/home/node
16
 
17
- # Make sure the `node` account picks up /usr/local/bin
18
  ENV PATH=/home/node/.local/bin:/usr/local/bin:${PATH}
19
 
20
- # Create a workspace directory the user can clone projects into and own it.
21
  RUN mkdir -p $HOME/workspace && chown -R node:node $HOME
22
 
23
- # Switch to the existing UID 1000 user
 
 
 
 
24
  USER node
25
  WORKDIR $HOME/workspace
26
 
27
- # The port must match `app_port` in the README's YAML block.
28
  EXPOSE 4096
29
 
30
- # Start the OpenCode web server.
31
- CMD ["opencode", "serve", "--port", "4096", "--hostname", "0.0.0.0"]
 
1
  FROM node:20-bookworm-slim
2
 
3
+ # System dependencies including Python for the sync script
4
  RUN apt-get update && apt-get install -y --no-install-recommends \
5
  git \
6
  ca-certificates \
7
  curl \
8
+ python3 \
9
+ python3-pip \
10
  && rm -rf /var/lib/apt/lists/*
11
 
 
12
  RUN npm install -g opencode-ai
13
 
14
+ # Install HF Hub for data synchronization
15
+ RUN pip3 install huggingface_hub --break-system-packages
 
16
 
17
+ ENV HOME=/home/node
18
  ENV PATH=/home/node/.local/bin:/usr/local/bin:${PATH}
19
 
 
20
  RUN mkdir -p $HOME/workspace && chown -R node:node $HOME
21
 
22
+ # Copy the custom logic files into the container
23
+ COPY sync.py $HOME/sync.py
24
+ COPY start.sh $HOME/start.sh
25
+ RUN chown node:node $HOME/sync.py $HOME/start.sh && chmod +x $HOME/start.sh
26
+
27
  USER node
28
  WORKDIR $HOME/workspace
29
 
 
30
  EXPOSE 4096
31
 
32
+ CMD ["/home/node/start.sh"]
 
start.sh ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Launch the sync agent in the background
4
+ python3 /home/node/sync.py &
5
+
6
+ # Start the primary web server
7
+ opencode web --port 4096 --hostname 0.0.0.0
sync.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import shutil
4
+ from pathlib import Path
5
+ from huggingface_hub import HfApi, snapshot_download
6
+
7
+ # Configuration fetched from Space Secrets
8
+ REPO_ID = os.getenv("DATASET_REPO_ID")
9
+ TOKEN = os.getenv("HF_TOKEN")
10
+
11
+ # Core directories
12
+ WORKSPACE_DIR = Path("/home/node/workspace")
13
+ OPENCODE_DIR = Path("/home/node/.local/share/opencode") # Default Linux storage
14
+ CACHE_DIR = Path("/home/node/.sync_cache")
15
+ INTERVAL = 30 * 60 # 30 minutes in seconds
16
+
17
+ api = HfApi(token=TOKEN)
18
+
19
+ def initial_pull():
20
+ if not REPO_ID:
21
+ print("No DATASET_REPO_ID provided. Skipping pull.")
22
+ return
23
+
24
+ print("Fetching dataset on startup...")
25
+ try:
26
+ # Download the latest snapshot of the private dataset
27
+ local_dir = snapshot_download(repo_id=REPO_ID, repo_type="dataset", token=TOKEN)
28
+
29
+ # Restore workspace files (includes env.example / env.json.local)
30
+ ds_workspace = Path(local_dir) / "workspace"
31
+ if ds_workspace.exists():
32
+ shutil.copytree(ds_workspace, WORKSPACE_DIR, dirs_exist_ok=True)
33
+
34
+ # Restore OpenCode sessions and history
35
+ ds_opencode = Path(local_dir) / "opencode"
36
+ if ds_opencode.exists():
37
+ shutil.copytree(ds_opencode, OPENCODE_DIR, dirs_exist_ok=True)
38
+
39
+ # Create an initial cache to track future deletions
40
+ shutil.copytree(OPENCODE_DIR, CACHE_DIR, dirs_exist_ok=True)
41
+ print("Restoration complete.")
42
+ except Exception as e:
43
+ print(f"Initial pull failed (likely an empty/new dataset): {e}")
44
+
45
+ def push_to_dataset():
46
+ if not REPO_ID:
47
+ return
48
+
49
+ print("Initiating 30-minute sync cycle...")
50
+ session_dir = OPENCODE_DIR / "storage" / "session"
51
+ cache_session_dir = CACHE_DIR / "storage" / "session"
52
+
53
+ # 1. Check for trashed sessions
54
+ if cache_session_dir.exists():
55
+ for cache_file in cache_session_dir.rglob("*.json"):
56
+ relative_path = cache_file.relative_to(cache_session_dir)
57
+ current_file = session_dir / relative_path
58
+
59
+ if not current_file.exists():
60
+ print(f"Detected deleted session: {relative_path}. Moving to trash...")
61
+ api.upload_file(
62
+ path_or_fileobj=str(cache_file),
63
+ path_in_repo=f"trashed/sessions/{relative_path}",
64
+ repo_id=REPO_ID,
65
+ repo_type="dataset",
66
+ token=TOKEN
67
+ )
68
+
69
+ # 2. Sync workspace and configuration
70
+ # The API automatically ignores the push if files are unchanged/empty.
71
+ # Files like env.json.local will upload securely since the dataset is private.
72
+ api.upload_folder(
73
+ folder_path=str(WORKSPACE_DIR),
74
+ path_in_repo="workspace",
75
+ repo_id=REPO_ID,
76
+ repo_type="dataset",
77
+ token=TOKEN,
78
+ ignore_patterns=["**/__pycache__/*", "**/.git/*"]
79
+ )
80
+
81
+ api.upload_folder(
82
+ folder_path=str(OPENCODE_DIR),
83
+ path_in_repo="opencode",
84
+ repo_id=REPO_ID,
85
+ repo_type="dataset",
86
+ token=TOKEN
87
+ )
88
+
89
+ # 3. Rebuild local deletion cache
90
+ if CACHE_DIR.exists():
91
+ shutil.rmtree(CACHE_DIR)
92
+ shutil.copytree(OPENCODE_DIR, CACHE_DIR, dirs_exist_ok=True)
93
+ print("Sync cycle finished successfully.")
94
+
95
+ if __name__ == "__main__":
96
+ initial_pull()
97
+ while True:
98
+ time.sleep(INTERVAL)
99
+ push_to_dataset()