Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package knotserver 2 3import ( 4 "context" 5 "fmt" 6 "os" 7 "syscall" 8 9 "github.com/urfave/cli/v3" 10 "tangled.org/core/hook" 11 "tangled.org/core/knotserver/db" 12 "tangled.org/core/knotserver/sandbox" 13 "tangled.org/core/log" 14) 15 16// MigrateIsolationCommand returns the CLI command for migrate-isolation. 17func MigrateIsolationCommand() *cli.Command { 18 return &cli.Command{ 19 Name: "migrate-isolation", 20 Usage: "chown existing repos to their owner virtual UIDs and refresh hooks (run once before enabling --secure-mode)", 21 Action: RunMigrateIsolation, 22 Flags: []cli.Flag{ 23 &cli.StringFlag{ 24 Name: "git-dir", 25 Usage: "base directory for git repos", 26 Value: "/home/git", 27 }, 28 &cli.StringFlag{ 29 Name: "db", 30 Usage: "path to knotserver SQLite database", 31 Value: "knotserver.db", 32 }, 33 &cli.StringFlag{ 34 Name: "internal-api", 35 Usage: "internal API address for hook configuration", 36 Value: "127.0.0.1:5444", 37 }, 38 &cli.BoolFlag{ 39 Name: "force", 40 Usage: "re-run chown/chmod even on already-migrated repos", 41 }, 42 }, 43 } 44} 45 46// RunMigrateIsolation iterates over all repos in the DB, assigns virtual UIDs 47// to their owners, chowns the repo trees recursively, and records isolated_at. 48func RunMigrateIsolation(ctx context.Context, cmd *cli.Command) error { 49 logger := log.FromContext(ctx) 50 logger = log.SubLogger(logger, "migrate-isolation") 51 52 gitDir := cmd.String("git-dir") 53 dbPath := cmd.String("db") 54 internalApi := cmd.String("internal-api") 55 56 serviceGid, err := sandbox.ServiceGid(gitDir) 57 if err != nil { 58 return fmt.Errorf("resolve service gid: %w", err) 59 } 60 61 hookCfg := hook.Config( 62 hook.WithScanPath(gitDir), 63 hook.WithInternalApi(internalApi), 64 ) 65 66 d, err := db.Setup(ctx, dbPath) 67 if err != nil { 68 return fmt.Errorf("failed to open db: %w", err) 69 } 70 71 repos, err := d.AllReposForMigration(cmd.Bool("force")) 72 if err != nil { 73 return fmt.Errorf("failed to list repos: %w", err) 74 } 75 76 if len(repos) == 0 { 77 logger.Info("no repos need migration") 78 return nil 79 } 80 81 logger.Info("starting isolation migration", "total", len(repos)) 82 83 var migrated, skipped, failed int 84 85 for _, repo := range repos { 86 repoPath, _, _, err := d.ResolveRepoDIDOnDisk(gitDir, repo.RepoDID) 87 if err != nil { 88 logger.Error("repo not on disk, skipping", 89 "repo_did", repo.RepoDID, "error", err) 90 skipped++ 91 continue 92 } 93 94 ownerUID, err := d.GetOrAssignOwnerUID(repo.OwnerDID) 95 if err != nil { 96 logger.Error("failed to get/assign UID", 97 "repo_did", repo.RepoDID, "owner_did", repo.OwnerDID, "error", err) 98 failed++ 99 continue 100 } 101 102 // regenerate hooks first so the chown walk preserves their 0755 mode 103 // via the executable-bit check in ChownRepoTree. 104 if err := hook.SetupRepo(hookCfg, repoPath); err != nil { 105 logger.Error("failed to set up hooks", 106 "repo_did", repo.RepoDID, "path", repoPath, "error", err) 107 failed++ 108 continue 109 } 110 111 if err := sandbox.ChmodRepoTree(repoPath); err != nil { 112 logger.Error("chmod failed", 113 "repo_did", repo.RepoDID, "path", repoPath, "error", err) 114 failed++ 115 continue 116 } 117 118 if err := sandbox.ChownRepoTree(repoPath, int(ownerUID), int(serviceGid)); err != nil { 119 // EPERM: process lacks cap_chown; log and continue rather than 120 // aborting so all failures are reported. 121 if isEPERM(err) { 122 logger.Error("chown failed: process lacks cap_chown "+ 123 "(run as root or grant cap_chown to the binary)", 124 "repo_did", repo.RepoDID, "path", repoPath, "uid", ownerUID) 125 } else { 126 logger.Error("chown failed", 127 "repo_did", repo.RepoDID, "path", repoPath, "uid", ownerUID, "error", err) 128 } 129 failed++ 130 continue 131 } 132 133 if err := d.MarkRepoIsolated(repo.RepoDID); err != nil { 134 logger.Error("failed to record isolated_at", 135 "repo_did", repo.RepoDID, "error", err) 136 failed++ 137 continue 138 } 139 140 logger.Info("migrated repo", 141 "repo_did", repo.RepoDID, "owner_did", repo.OwnerDID, "uid", ownerUID, "path", repoPath) 142 migrated++ 143 } 144 145 logger.Info("migration complete", 146 "migrated", migrated, "skipped", skipped, "failed", failed, "total", len(repos)) 147 148 if failed > 0 { 149 return fmt.Errorf("%d repo(s) failed to migrate", failed) 150 } 151 return nil 152} 153 154func isEPERM(err error) bool { 155 if pathErr, ok := err.(*os.PathError); ok { 156 return pathErr.Err == syscall.EPERM 157 } 158 return false 159}