@@ -10,6 +10,8 @@ import (
1010 "context"
1111 "encoding/json"
1212 "fmt"
13+ "os"
14+ "path/filepath"
1315
1416 "cubeos-api/internal/flowengine/activities"
1517 "cubeos-api/internal/managers"
@@ -82,35 +84,101 @@ func (a *appConflictAdapter) AppExists(ctx context.Context, name string) (bool,
8284
8385// --- appStoreManifestAdapter: activities.AppStoreManifestReader via *managers.AppStoreManager ---
8486//
85- // Session 1: ReadManifest is functional. ProcessManifest, RemapVolumes, and DetectWebUIType
86- // are stubs that return errors — the active install path still uses InstallAppWithProgress.
87- // Full implementations land in Session 3 when AppStoreManager is gutted.
87+ // ReadManifest reads the manifest YAML from disk and returns a JSON blob containing
88+ // the manifest content (manifest_yaml), app metadata, and store-specific hints.
89+ // This bypasses the json:"-" tag on StoreApp.ManifestPath.
90+ //
91+ // ProcessManifest applies CasaOS variable substitution, Swarm sanitization, and
92+ // port remapping using the allocated port from the fat envelope.
93+ //
94+ // RemapVolumes remaps external bind-mount paths to safe defaults under /cubeos/apps/.
95+ //
96+ // DetectWebUIType is a stub — the actual detection is performed by the app.detect_webui
97+ // activity (database.go) via HTTP probe. This method is never called.
8898
8999type appStoreManifestAdapter struct { mgr * managers.AppStoreManager }
90100
101+ // manifestPayload is the JSON structure embedded in ReadManifestOutput.Manifest.
102+ // All fields are consumed by ProcessManifest and RemapVolumes via the fat envelope.
103+ type manifestPayload struct {
104+ AppName string `json:"app_name"`
105+ DataPath string `json:"data_path"`
106+ ManifestYAML string `json:"manifest_yaml"`
107+ PortMap string `json:"port_map,omitempty"` // CasaOS x-casaos port hint
108+ }
109+
91110func (a * appStoreManifestAdapter ) ReadManifest (ctx context.Context , storeID , appName string ) (json.RawMessage , error ) {
92111 app := a .mgr .GetApp (storeID , appName )
93112 if app == nil {
94113 return nil , fmt .Errorf ("app %s/%s not found in catalog" , storeID , appName )
95114 }
96- data , err := json .Marshal (app )
115+ if app .ManifestPath == "" {
116+ return nil , fmt .Errorf ("manifest path not set for %s/%s" , storeID , appName )
117+ }
118+
119+ // Read the raw YAML from disk (ManifestPath has json:"-" so it's not in catalog JSON).
120+ raw , err := os .ReadFile (app .ManifestPath )
121+ if err != nil {
122+ return nil , fmt .Errorf ("failed to read manifest for %s/%s: %w" , storeID , appName , err )
123+ }
124+
125+ payload := manifestPayload {
126+ AppName : appName ,
127+ DataPath : filepath .Join ("/cubeos/apps" , appName , "appdata" ),
128+ ManifestYAML : string (raw ),
129+ PortMap : app .PortMap ,
130+ }
131+ data , err := json .Marshal (payload )
97132 if err != nil {
98- return nil , fmt .Errorf ("marshal manifest for %s/%s: %w" , storeID , appName , err )
133+ return nil , fmt .Errorf ("failed to marshal manifest payload for %s/%s: %w" , storeID , appName , err )
99134 }
100135 return json .RawMessage (data ), nil
101136}
102137
103- func (a * appStoreManifestAdapter ) ProcessManifest (ctx context.Context , manifest json.RawMessage ) (* activities.ProcessedManifest , error ) {
104- // Session 1 stub: full implementation in Session 3.
105- return nil , fmt .Errorf ("ProcessManifest: not yet implemented (Session 3)" )
138+ func (a * appStoreManifestAdapter ) ProcessManifest (ctx context.Context , manifest json.RawMessage , port int ) (* activities.ProcessedManifest , error ) {
139+ var payload manifestPayload
140+ if err := json .Unmarshal (manifest , & payload ); err != nil {
141+ return nil , fmt .Errorf ("failed to unmarshal manifest payload: %w" , err )
142+ }
143+ if payload .ManifestYAML == "" {
144+ return nil , fmt .Errorf ("manifest_yaml is empty" )
145+ }
146+ if payload .AppName == "" {
147+ return nil , fmt .Errorf ("app_name is missing from manifest payload" )
148+ }
149+ if payload .DataPath == "" {
150+ payload .DataPath = filepath .Join ("/cubeos/apps" , payload .AppName , "appdata" )
151+ }
152+
153+ // Apply CasaOS variable substitution + Swarm sanitization.
154+ processed := a .mgr .ProcessManifestYAML (payload .ManifestYAML , payload .AppName , payload .DataPath )
155+
156+ // Remap the published host port to the CubeOS-allocated port.
157+ if port > 0 {
158+ remapped , err := managers .RemapPorts (processed , port , payload .PortMap )
159+ if err == nil {
160+ processed = remapped
161+ }
162+ // non-fatal: if remapping fails the original is used
163+ }
164+
165+ return & activities.ProcessedManifest {
166+ ComposeYAML : processed ,
167+ }, nil
106168}
107169
108170func (a * appStoreManifestAdapter ) RemapVolumes (ctx context.Context , compose string , appName string ) (string , error ) {
109- // Session 1 stub: return compose unchanged; full implementation in Session 3.
110- return compose , nil
171+ dataPath := filepath .Join ("/cubeos/apps" , appName , "appdata" )
172+ remapped , _ , err := managers .RemapExternalVolumes (compose , appName , dataPath , map [string ]string {})
173+ if err != nil {
174+ // non-fatal: return original compose if remapping fails
175+ return compose , nil
176+ }
177+ return remapped , nil
111178}
112179
113180func (a * appStoreManifestAdapter ) DetectWebUIType (ctx context.Context , manifest json.RawMessage ) (string , error ) {
114- // Session 1 stub: default to "http".
115- return "http" , nil
181+ // Detection is performed by the app.detect_webui activity (database.go) via HTTP probe.
182+ // This method satisfies the interface but is never called by any registered activity.
183+ return "browser" , nil
116184}
0 commit comments