udbhav commited on
Commit
0fc713e
Β·
1 Parent(s): 9981eb3

added multi user

Browse files
Files changed (2) hide show
  1. Dockerfile +14 -12
  2. app.py +95 -34
Dockerfile CHANGED
@@ -44,18 +44,19 @@ RUN ln -sf /usr/bin/python3.12 /usr/bin/python && \
44
 
45
  # ---------------- Writable app data & HF cache ----------------
46
  ENV APP_DATA_DIR=/data
47
- ENV HF_HOME=/data/hf_home
48
- ENV HUGGINGFACE_HUB_CACHE=/data/hf_home
49
- ENV TRANSFORMERS_CACHE=/data/hf_home
50
- ENV MPLCONFIGDIR=/data/matplotlib
51
 
52
- RUN mkdir -p /data/geometry /data/solution /data/weights /data/hf_home /data/matplotlib \
 
53
  && chmod -R 777 /data
54
 
55
  # ---------------- Install frpc for Gradio share=True ----------------
56
- RUN mkdir -p /data/hf_home/gradio/frpc && \
57
- wget https://cdn-media.huggingface.co/frpc-gradio-0.3/frpc_linux_amd64 -O /data/hf_home/gradio/frpc/frpc_linux_amd64_v0.3 && \
58
- chmod +x /data/hf_home/gradio/frpc/frpc_linux_amd64_v0.3
59
 
60
  # ---------------- Application setup ----------------
61
  WORKDIR /app
@@ -81,9 +82,10 @@ RUN chown -R 1000:1000 /app
81
  EXPOSE 7860
82
 
83
  # Set HOST and PORT via environment variables
84
- ENV HOST=0.0.0.0
85
- ENV PORT=7860
86
 
87
- # Run app
88
- CMD ["python", "app.py"]
 
89
 
 
44
 
45
  # ---------------- Writable app data & HF cache ----------------
46
  ENV APP_DATA_DIR=/data
47
+ ENV HF_HOME=/data/shared/hf_home
48
+ ENV HUGGINGFACE_HUB_CACHE=/data/shared/hf_home
49
+ ENV TRANSFORMERS_CACHE=/data/shared/hf_home
50
+ ENV MPLCONFIGDIR=/data/matplotlib
51
 
52
+ # Create shared directories (for models/HF cache) and per-user directories will be created at runtime
53
+ RUN mkdir -p /data/shared/weights /data/shared/hf_home /data/matplotlib \
54
  && chmod -R 777 /data
55
 
56
  # ---------------- Install frpc for Gradio share=True ----------------
57
+ RUN mkdir -p /data/shared/hf_home/gradio/frpc && \
58
+ wget https://cdn-media.huggingface.co/frpc-gradio-0.3/frpc_linux_amd64 -O /data/shared/hf_home/gradio/frpc/frpc_linux_amd64_v0.3 && \
59
+ chmod +x /data/shared/hf_home/gradio/frpc/frpc_linux_amd64_v0.3
60
 
61
  # ---------------- Application setup ----------------
62
  WORKDIR /app
 
82
  EXPOSE 7860
83
 
84
  # Set HOST and PORT via environment variables
85
+ # ENV HOST=0.0.0.0
86
+ # ENV PORT=7860
87
 
88
+ # Run app with trame.tools.serve; bind explicitly to 0.0.0.0:7860
89
+ # This uses the create_app factory pattern for proper multi-user session management
90
+ CMD ["python", "-m", "trame.tools.serve", "--exec", "app:create_app", "--host", "0.0.0.0", "--port", "7860"]
91
 
app.py CHANGED
@@ -20,11 +20,25 @@ import traceback
20
  import signal
21
 
22
 
23
- # Writable base dir
24
- DATA_DIR = os.environ.get("APP_DATA_DIR", os.path.join(tempfile.gettempdir(), "appdata"))
 
25
  os.makedirs(DATA_DIR, exist_ok=True)
 
26
  os.environ.setdefault("MPLCONFIGDIR", DATA_DIR)
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  def setup_environment():
29
  cache_dir = Path("cache")
30
  cache_dir.mkdir(exist_ok=True)
@@ -73,7 +87,27 @@ def download_private_repo(cache_dir, repo_id, hf_token):
73
 
74
  return repo_path
75
 
76
- def load_private_app(repo_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  sys.path.insert(0, str(repo_path))
78
  try:
79
  import app as private_app
@@ -87,23 +121,56 @@ def load_private_app(repo_path):
87
  # Thumbnails are optional; don't break startup if this fails.
88
  traceback.print_exc()
89
 
 
 
 
 
90
  # Check for app or demo attribute
91
- if hasattr(private_app, "app"):
92
- return private_app.app
93
  elif hasattr(private_app, "demo"):
94
- return private_app.demo
95
- # Check if PFMDemo class exists and instantiate it
96
  elif hasattr(private_app, "PFMDemo"):
97
- app_instance = private_app.PFMDemo()
98
- app_instance.server.controller.add("decimate_again", app_instance.decimate_again)
99
- app_instance.server.controller.add("reset_mesh", app_instance.reset_mesh)
100
- return app_instance
101
- raise RuntimeError("No demo/app found in private repo")
 
 
 
 
 
 
 
 
 
 
102
  except Exception as e:
103
  traceback.print_exc()
104
  raise RuntimeError(f"Failed to load app from private repo: {e}")
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  def main():
 
 
 
 
107
  # Setup signal handlers for graceful shutdown
108
  def signal_handler(sig, frame):
109
  print("\nπŸ›‘ Received shutdown signal, cleaning up...")
@@ -113,37 +180,31 @@ def main():
113
  signal.signal(signal.SIGTERM, signal_handler)
114
 
115
  try:
116
- hf_token = os.environ.get("HF_TOKEN")
117
- repo_id = os.environ.get("REPO_ID")
118
-
119
- if not hf_token or not repo_id:
120
- raise ValueError("HF_TOKEN and REPO_ID must be set")
121
-
122
- print(f"πŸš€ Starting app with repo_id: {repo_id}")
123
  print(f"πŸ“‚ Current working directory: {os.getcwd()}")
124
 
125
- cache_dir = setup_environment()
126
- repo_path = download_private_repo(cache_dir, repo_id, hf_token)
127
- demo = load_private_app(repo_path)
 
 
 
 
 
128
 
129
- # Check if it's a Trame app (has server.start method) or Gradio app (has launch method)
130
  if hasattr(demo, 'server') and hasattr(demo.server, 'start'):
131
  # Trame app
132
- port = int(os.environ.get("PORT", "7860"))
133
- host = os.environ.get("HOST", "0.0.0.0") # Allow host to be configured via env var
134
-
135
- print(f"πŸš€ Starting Trame server on {host}:{port}")
136
 
137
- if demo.server.state.health == "running":
138
  print("Trame server already running, not starting again.")
139
  return
140
 
141
- # Configure server to bind to the specified host
142
  try:
143
  # Try with host parameter first (if supported)
144
  demo.server.start(
145
- host=host,
146
- port=port,
147
  open_browser=False,
148
  show_connection_info=True,
149
  backend="aiohttp",
@@ -153,7 +214,6 @@ def main():
153
  # If host parameter is not supported, configure via server options
154
  # Most Trame versions bind to 0.0.0.0 by default with aiohttp
155
  demo.server.start(
156
- port=port,
157
  open_browser=False,
158
  show_connection_info=True,
159
  backend="aiohttp",
@@ -161,12 +221,13 @@ def main():
161
  )
162
  elif hasattr(demo, 'launch'):
163
  # Gradio app
 
164
  demo.launch(
165
  server_name="0.0.0.0",
166
- server_port=int(os.environ.get("PORT", "7860")),
167
  share=False,
168
  show_error=True,
169
- allowed_paths=[str(repo_path), DATA_DIR]
170
  )
171
  else:
172
  raise RuntimeError("App does not have launch() or server.start() method")
 
20
  import signal
21
 
22
 
23
+ # Writable base dir (matches alphaLPFM pattern)
24
+ # Use APP_DATA_DIR to match alphaLPFM's expectation
25
+ DATA_DIR = os.environ.get("APP_DATA_DIR", os.path.join(tempfile.gettempdir(), "appdata_trame"))
26
  os.makedirs(DATA_DIR, exist_ok=True)
27
+ os.environ.setdefault("APP_DATA_DIR", DATA_DIR) # Ensure it's set for the private app
28
  os.environ.setdefault("MPLCONFIGDIR", DATA_DIR)
29
 
30
+ # Set up shared directories structure (matches alphaLPFM)
31
+ SHARED_DIR = os.path.join(DATA_DIR, "shared")
32
+ SHARED_HF_DIR = os.path.join(SHARED_DIR, "hf_home")
33
+ SHARED_WEIGHTS_DIR = os.path.join(SHARED_DIR, "weights")
34
+ for d in (SHARED_DIR, SHARED_HF_DIR, SHARED_WEIGHTS_DIR):
35
+ os.makedirs(d, exist_ok=True)
36
+
37
+ # Set HF cache environment variables (matches alphaLPFM)
38
+ os.environ.setdefault("HF_HOME", SHARED_HF_DIR)
39
+ os.environ.setdefault("HUGGINGFACE_HUB_CACHE", SHARED_HF_DIR)
40
+ os.environ.setdefault("TRANSFORMERS_CACHE", SHARED_HF_DIR)
41
+
42
  def setup_environment():
43
  cache_dir = Path("cache")
44
  cache_dir.mkdir(exist_ok=True)
 
87
 
88
  return repo_path
89
 
90
+ # Cache for loaded private app
91
+ _PRIVATE_APP_CACHE = None
92
+ _PRIVATE_APP_CREATE_APP = None
93
+
94
+ def _load_private_app_once():
95
+ """Load the private app once and cache it. Called by create_app() or main()."""
96
+ global _PRIVATE_APP_CACHE, _PRIVATE_APP_CREATE_APP
97
+
98
+ if _PRIVATE_APP_CACHE is not None:
99
+ return _PRIVATE_APP_CACHE, _PRIVATE_APP_CREATE_APP
100
+
101
+ hf_token = os.environ.get("HF_TOKEN")
102
+ repo_id = os.environ.get("REPO_ID")
103
+
104
+ if not hf_token or not repo_id:
105
+ raise ValueError("HF_TOKEN and REPO_ID must be set")
106
+
107
+ print(f"πŸ” Loading private app from repo_id: {repo_id}")
108
+ cache_dir = setup_environment()
109
+ repo_path = download_private_repo(cache_dir, repo_id, hf_token)
110
+
111
  sys.path.insert(0, str(repo_path))
112
  try:
113
  import app as private_app
 
121
  # Thumbnails are optional; don't break startup if this fails.
122
  traceback.print_exc()
123
 
124
+ # βœ… PREFER create_app factory (for multi-user session management)
125
+ if hasattr(private_app, "create_app"):
126
+ print("βœ… Found create_app factory in private app (supports multi-user sessions)")
127
+ _PRIVATE_APP_CREATE_APP = private_app.create_app
128
  # Check for app or demo attribute
129
+ elif hasattr(private_app, "app"):
130
+ _PRIVATE_APP_CREATE_APP = lambda: private_app.app
131
  elif hasattr(private_app, "demo"):
132
+ _PRIVATE_APP_CREATE_APP = lambda: private_app.demo
133
+ # Check if PFMDemo class exists and instantiate it (fallback, but not ideal for multi-user)
134
  elif hasattr(private_app, "PFMDemo"):
135
+ print("⚠️ Using PFMDemo directly (may not support multi-user sessions)")
136
+ def _create_pfmdemo_app(server=None):
137
+ from trame.app import get_server
138
+ if server is None:
139
+ server = get_server()
140
+ app_instance = private_app.PFMDemo(server)
141
+ app_instance.server.controller.add("decimate_again", app_instance.decimate_again)
142
+ app_instance.server.controller.add("reset_mesh", app_instance.reset_mesh)
143
+ return app_instance
144
+ _PRIVATE_APP_CREATE_APP = _create_pfmdemo_app
145
+ else:
146
+ raise RuntimeError("No demo/app/create_app found in private repo")
147
+
148
+ _PRIVATE_APP_CACHE = (private_app, repo_path)
149
+ return _PRIVATE_APP_CACHE, _PRIVATE_APP_CREATE_APP
150
  except Exception as e:
151
  traceback.print_exc()
152
  raise RuntimeError(f"Failed to load app from private repo: {e}")
153
 
154
+ def create_app(server=None, **kwargs):
155
+ """
156
+ Trame application factory (matches alphaLPFM pattern).
157
+
158
+ This function is called by trame.tools.serve to create the app instance.
159
+ It loads the private app and returns its create_app result (or wraps it).
160
+ """
161
+ _, create_app_fn = _load_private_app_once()
162
+
163
+ if create_app_fn is None:
164
+ raise RuntimeError("No create_app function found in private app")
165
+
166
+ # Call the private app's create_app (or wrapper)
167
+ return create_app_fn(server=server, **kwargs)
168
+
169
  def main():
170
+ """
171
+ Main entry point (fallback if not using trame.tools.serve).
172
+ This is kept for backward compatibility but trame.tools.serve is preferred.
173
+ """
174
  # Setup signal handlers for graceful shutdown
175
  def signal_handler(sig, frame):
176
  print("\nπŸ›‘ Received shutdown signal, cleaning up...")
 
180
  signal.signal(signal.SIGTERM, signal_handler)
181
 
182
  try:
183
+ print(f"πŸš€ Starting app (using main() - consider using trame.tools.serve for multi-user support)")
 
 
 
 
 
 
184
  print(f"πŸ“‚ Current working directory: {os.getcwd()}")
185
 
186
+ # Load private app (will be cached)
187
+ _, create_app_fn = _load_private_app_once()
188
+
189
+ if create_app_fn is None:
190
+ raise RuntimeError("No create_app function found in private app")
191
+
192
+ # Create app instance
193
+ demo = create_app()
194
 
195
+ # Check if it's a Trame app (has server.start method)
196
  if hasattr(demo, 'server') and hasattr(demo.server, 'start'):
197
  # Trame app
198
+ print(f"πŸš€ Starting Trame server (host/port from trame.tools.serve or defaults)")
 
 
 
199
 
200
+ if hasattr(demo.server.state, 'health') and demo.server.state.health == "running":
201
  print("Trame server already running, not starting again.")
202
  return
203
 
204
+ # Configure server - host/port come from trame.tools.serve or environment
205
  try:
206
  # Try with host parameter first (if supported)
207
  demo.server.start(
 
 
208
  open_browser=False,
209
  show_connection_info=True,
210
  backend="aiohttp",
 
214
  # If host parameter is not supported, configure via server options
215
  # Most Trame versions bind to 0.0.0.0 by default with aiohttp
216
  demo.server.start(
 
217
  open_browser=False,
218
  show_connection_info=True,
219
  backend="aiohttp",
 
221
  )
222
  elif hasattr(demo, 'launch'):
223
  # Gradio app
224
+ _, repo_path = _PRIVATE_APP_CACHE if _PRIVATE_APP_CACHE else (None, None)
225
  demo.launch(
226
  server_name="0.0.0.0",
227
+ server_port=7860,
228
  share=False,
229
  show_error=True,
230
+ allowed_paths=[str(repo_path) if repo_path else ".", DATA_DIR]
231
  )
232
  else:
233
  raise RuntimeError("App does not have launch() or server.start() method")