@@ -336,34 +336,51 @@ func newAccessoryBackupCmd(flags *Flags) *cobra.Command {
336336 region string
337337 endpoint string
338338 schedule string
339+ appName string
340+ local bool
339341 )
340342
341343 cmd := & cobra.Command {
342344 Use : "backup <name>" ,
343345 Short : "Back up an accessory (database-aware dump)" ,
344346 Args : cobra .ExactArgs (1 ),
345347 RunE : func (cmd * cobra.Command , args []string ) error {
346- return runAccessoryBackup (flags , args [0 ], bucket , region , endpoint , schedule )
348+ return runAccessoryBackup (flags , appName , args [0 ], bucket , region , endpoint , schedule , local )
347349 },
348350 }
349351
350352 cmd .Flags ().StringVar (& bucket , "bucket" , "" , "S3 bucket name" )
351353 cmd .Flags ().StringVar (& region , "region" , "us-east-1" , "AWS region" )
352354 cmd .Flags ().StringVar (& endpoint , "endpoint" , "" , "S3-compatible endpoint URL (MinIO/B2/R2); creds from TEPLOY_S3_ACCESS_KEY/SECRET_KEY or AWS_* env" )
353355 cmd .Flags ().StringVar (& schedule , "schedule" , "" , "cron schedule for automated backups" )
356+ cmd .Flags ().StringVar (& appName , "app" , "" , "app name — read accessory image/env from server state instead of teploy.yml" )
357+ cmd .Flags ().BoolVar (& local , "local" , false , "run against the local docker host (the mode scheduled cron jobs use; requires --app)" )
354358
355359 return cmd
356360}
357361
358- func runAccessoryBackup (flags * Flags , name , bucket , region , endpoint , schedule string ) error {
359- appCfg , err := loadAppCfgForAccessory ()
360- if err != nil {
361- return err
362+ func runAccessoryBackup (flags * Flags , appName , name , bucket , region , endpoint , schedule string , local bool ) error {
363+ if local && appName == "" {
364+ return fmt .Errorf ("--local requires --app (no teploy.yml exists on the server)" )
362365 }
363366
364- accCfg , ok := appCfg .Accessories [name ]
365- if ! ok {
366- return fmt .Errorf ("accessory %q not found in teploy.yml" , name )
367+ var app string
368+ var accImage string
369+ var accEnv map [string ]string
370+ if ! local {
371+ appCfg , err := loadAppCfgForAccessory ()
372+ if err != nil {
373+ return err
374+ }
375+ accCfg , ok := appCfg .Accessories [name ]
376+ if ! ok {
377+ return fmt .Errorf ("accessory %q not found in teploy.yml" , name )
378+ }
379+ app = appCfg .App
380+ accImage = accCfg .Image
381+ accEnv = accCfg .Env
382+ } else {
383+ app = appName
367384 }
368385
369386 if bucket == "" {
@@ -384,32 +401,61 @@ func runAccessoryBackup(flags *Flags, name, bucket, region, endpoint, schedule s
384401 ctx , cancel := signal .NotifyContext (context .Background (), os .Interrupt )
385402 defer cancel ()
386403
387- executor , err := connectForApp (ctx , flags , appCfg )
388- if err != nil {
389- return err
404+ // --local runs on the server itself (the mode scheduled cron jobs use):
405+ // docker + aws execute directly, no SSH hop, no teploy.yml — the
406+ // accessory's image and env are read from the running container.
407+ var executor ssh.Executor
408+ if local {
409+ executor = ssh .NewLocalExecutor ()
410+ } else {
411+ appCfg , err := loadAppCfgForAccessory ()
412+ if err != nil {
413+ return err
414+ }
415+ executor , err = connectForApp (ctx , flags , appCfg )
416+ if err != nil {
417+ return err
418+ }
390419 }
391420 defer executor .Close ()
392421
393422 client := backup .NewClient (executor , os .Stdout )
394423 s3Cfg := s3Config (bucket , region , endpoint )
395424
425+ if local {
426+ image , env , err := client .InspectAccessory (ctx , app , name )
427+ if err != nil {
428+ return err
429+ }
430+ accImage , accEnv = image , env
431+ }
432+
396433 if schedule != "" {
397- backupCmd := fmt .Sprintf ("teploy accessory backup %s --bucket %s --region %s" , name , bucket , region )
434+ // The scheduled job runs ON the server: --local --app makes it
435+ // self-contained (no teploy.yml server-side), reading accessory
436+ // config from the running container.
437+ backupCmd := fmt .Sprintf ("teploy accessory backup %s --local --app %s --bucket %s --region %s" ,
438+ name , app , bucket , region )
398439 if endpoint != "" {
399- // The cron job runs server-side where the local TEPLOY_S3_* env
400- // doesn't exist — embed endpoint + creds in the crontab line
401- // (root-only readable, same trust class as ~/.aws/credentials).
440+ // Cron has no TEPLOY_S3_* env — embed endpoint + creds in the
441+ // crontab line (root-only readable, same trust class as
442+ // ~/.aws/credentials).
402443 backupCmd = fmt .Sprintf ("TEPLOY_S3_ACCESS_KEY=%s TEPLOY_S3_SECRET_KEY=%s %s --endpoint %s" ,
403444 ssh .ShellQuote (s3Cfg .AccessKey ), ssh .ShellQuote (s3Cfg .SecretKey ), backupCmd , ssh .ShellQuote (endpoint ))
404445 }
405- if err := client .SetSchedule (ctx , schedule , backupCmd , "teploy-accessory-backup:" + appCfg .App + ":" + name ); err != nil {
446+ // The cron job needs the teploy binary on the server; ship it via
447+ // the existing checksum-verified pipeline (same as heal/autodeploy).
448+ if _ , err := deployTeployBinaryToServer (ctx , executor , "/usr/local/bin/teploy" ); err != nil {
449+ return fmt .Errorf ("installing teploy binary for the scheduled job: %w" , err )
450+ }
451+ if err := client .SetSchedule (ctx , schedule , backupCmd , "teploy-accessory-backup:" + app + ":" + name ); err != nil {
406452 return err
407453 }
408454 fmt .Printf ("Scheduled backup: %s\n " , schedule )
409455 return nil
410456 }
411457
412- return client .AccessoryBackup (ctx , appCfg . App , name , accCfg . Image , accCfg . Env , s3Cfg )
458+ return client .AccessoryBackup (ctx , app , name , accImage , accEnv , s3Cfg )
413459}
414460
415461func newAccessoryRestoreCmd (flags * Flags ) * cobra.Command {
0 commit comments