Detailed changes
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strings"
+ "time"
"github.com/charmbracelet/log"
"github.com/charmbracelet/soft-serve/git"
@@ -14,6 +15,7 @@ import (
"github.com/charmbracelet/soft-serve/server/backend/sqlite"
"github.com/charmbracelet/soft-serve/server/config"
"github.com/charmbracelet/soft-serve/server/utils"
+ gitm "github.com/gogs/git-module"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
"gopkg.in/yaml.v3"
@@ -25,6 +27,9 @@ var (
Short: "Migrate config to new format",
Hidden: true,
RunE: func(cmd *cobra.Command, _ []string) error {
+ // Disable logging timestamp
+ log.SetReportTimestamp(false)
+
keyPath := os.Getenv("SOFT_SERVE_KEY_PATH")
reposPath := os.Getenv("SOFT_SERVE_REPO_PATH")
bindAddr := os.Getenv("SOFT_SERVE_BIND_ADDRESS")
@@ -46,7 +51,7 @@ var (
// Copy SSH host key
log.Info("Copying SSH host key...")
if keyPath != "" {
- if err := os.MkdirAll(filepath.Join(cfg.DataPath, "ssh"), 0700); err != nil {
+ if err := os.MkdirAll(filepath.Join(cfg.DataPath, "ssh"), os.ModePerm); err != nil {
return fmt.Errorf("failed to create ssh directory: %w", err)
}
@@ -104,6 +109,9 @@ var (
}
}
+ readme, readmePath, err := git.LatestFile(r, "README*")
+ hasReadme := err == nil
+
// Set server name
cfg.Name = ocfg.Name
@@ -125,13 +133,17 @@ var (
// Copy repos
if reposPath != "" {
log.Info("Copying repos...")
+ if err := os.MkdirAll(filepath.Join(cfg.DataPath, "repos"), os.ModePerm); err != nil {
+ return fmt.Errorf("failed to create repos directory: %w", err)
+ }
+
dirs, err := os.ReadDir(reposPath)
if err != nil {
return fmt.Errorf("failed to read repos directory: %w", err)
}
for _, dir := range dirs {
- if !dir.IsDir() {
+ if !dir.IsDir() || dir.Name() == "config" {
continue
}
@@ -140,12 +152,12 @@ var (
}
log.Infof(" Copying repo %s", dir.Name())
- if err := os.MkdirAll(filepath.Join(cfg.DataPath, "repos"), 0700); err != nil {
- return fmt.Errorf("failed to create repos directory: %w", err)
+ src := filepath.Join(reposPath, utils.SanitizeRepo(dir.Name()))
+ dst := filepath.Join(cfg.DataPath, "repos", utils.SanitizeRepo(dir.Name())) + ".git"
+ if err := os.MkdirAll(dst, os.ModePerm); err != nil {
+ return fmt.Errorf("failed to create repo directory: %w", err)
}
- src := utils.SanitizeRepo(filepath.Join(reposPath, dir.Name()))
- dst := utils.SanitizeRepo(filepath.Join(cfg.DataPath, "repos", dir.Name())) + ".git"
if err := copyDir(src, dst); err != nil {
return fmt.Errorf("failed to copy repo: %w", err)
}
@@ -154,26 +166,100 @@ var (
fmt.Fprintf(os.Stderr, "failed to create repository: %s\n", err)
}
}
+
+ if hasReadme {
+ log.Infof(" Copying readme from \"config\" to \".soft-serve\"")
+
+ // Switch to main branch
+ bcmd := git.NewCommand("branch", "-M", "main")
+
+ rp := filepath.Join(cfg.DataPath, "repos", ".soft-serve.git")
+ nr, err := git.Init(rp, true)
+ if err != nil {
+ return fmt.Errorf("failed to init repo: %w", err)
+ }
+
+ if _, err := nr.SymbolicRef("HEAD", gitm.RefsHeads+"main"); err != nil {
+ return fmt.Errorf("failed to set HEAD: %w", err)
+ }
+
+ tmpDir, err := os.MkdirTemp("", "soft-serve")
+ if err != nil {
+ return fmt.Errorf("failed to create temp dir: %w", err)
+ }
+
+ r, err := git.Init(tmpDir, false)
+ if err != nil {
+ return fmt.Errorf("failed to clone repo: %w", err)
+ }
+
+ if _, err := bcmd.RunInDir(tmpDir); err != nil {
+ return fmt.Errorf("failed to create main branch: %w", err)
+ }
+
+ if err := os.WriteFile(filepath.Join(tmpDir, readmePath), []byte(readme), 0o644); err != nil {
+ return fmt.Errorf("failed to write readme: %w", err)
+ }
+
+ if err := r.Add(gitm.AddOptions{
+ All: true,
+ }); err != nil {
+ return fmt.Errorf("failed to add readme: %w", err)
+ }
+
+ if err := r.Commit(&gitm.Signature{
+ Name: "Soft Serve",
+ Email: "vt100@charm.sh",
+ When: time.Now(),
+ }, "Add readme"); err != nil {
+ return fmt.Errorf("failed to commit readme: %w", err)
+ }
+
+ if err := r.RemoteAdd("origin", "file://"+rp); err != nil {
+ return fmt.Errorf("failed to add remote: %w", err)
+ }
+
+ if err := r.Push("origin", "main"); err != nil {
+ return fmt.Errorf("failed to push readme: %w", err)
+ }
+
+ // Create `.soft-serve` repository and add readme
+ if _, err := sb.CreateRepository(".soft-serve", backend.RepositoryOptions{
+ ProjectName: "Home",
+ Description: "Soft Serve home repository",
+ Hidden: true,
+ Private: false,
+ }); err != nil {
+ fmt.Fprintf(os.Stderr, "failed to create repository: %s\n", err)
+ }
+ }
}
// Set repos metadata & collabs
log.Info("Setting repos metadata & collabs...")
- for _, repo := range ocfg.Repos {
- if err := sb.SetProjectName(repo.Repo, repo.Name); err != nil {
- log.Errorf("failed to set repo name to %s: %s", repo.Repo, err)
+ for _, r := range ocfg.Repos {
+ repo, name := r.Repo, r.Name
+ // Special case for config repo
+ if repo == "config" {
+ repo = ".soft-serve"
+ r.Private = false
+ }
+
+ if err := sb.SetProjectName(repo, name); err != nil {
+ log.Errorf("failed to set repo name to %s: %s", repo, err)
}
- if err := sb.SetDescription(repo.Repo, repo.Note); err != nil {
- log.Errorf("failed to set repo description to %s: %s", repo.Repo, err)
+ if err := sb.SetDescription(repo, r.Note); err != nil {
+ log.Errorf("failed to set repo description to %s: %s", repo, err)
}
- if err := sb.SetPrivate(repo.Repo, repo.Private); err != nil {
- log.Errorf("failed to set repo private to %s: %s", repo.Repo, err)
+ if err := sb.SetPrivate(repo, r.Private); err != nil {
+ log.Errorf("failed to set repo private to %s: %s", repo, err)
}
- for _, collab := range repo.Collabs {
- if err := sb.AddCollaborator(repo.Repo, collab); err != nil {
- log.Errorf("failed to add repo collab to %s: %s", repo.Repo, err)
+ for _, collab := range r.Collabs {
+ if err := sb.AddCollaborator(repo, collab); err != nil {
+ log.Errorf("failed to add repo collab to %s: %s", repo, err)
}
}
}
@@ -291,11 +377,11 @@ func copyDir(src string, dst string) error {
if fd.IsDir() {
if err = copyDir(srcfp, dstfp); err != nil {
- fmt.Println(err)
+ log.Error("failed to copy directory", "err", err)
}
} else {
if err = copyFile(srcfp, dstfp); err != nil {
- fmt.Println(err)
+ log.Error("failed to copy file", "err", err)
}
}
}
@@ -0,0 +1,51 @@
+package git
+
+import (
+ "path/filepath"
+
+ "github.com/gobwas/glob"
+)
+
+// LatestFile returns the contents of the first file at the specified path pattern in the repository and its file path.
+func LatestFile(repo *Repository, pattern string) (string, string, error) {
+ g := glob.MustCompile(pattern)
+ dir := filepath.Dir(pattern)
+ head, err := repo.HEAD()
+ if err != nil {
+ return "", "", err
+ }
+ t, err := repo.TreePath(head, dir)
+ if err != nil {
+ return "", "", err
+ }
+ ents, err := t.Entries()
+ if err != nil {
+ return "", "", err
+ }
+ for _, e := range ents {
+ te := e
+ fp := filepath.Join(dir, te.Name())
+ if te.IsTree() {
+ continue
+ }
+ if g.Match(fp) {
+ if te.IsSymlink() {
+ bts, err := te.Contents()
+ if err != nil {
+ return "", "", err
+ }
+ fp = string(bts)
+ te, err = t.TreeEntry(fp)
+ if err != nil {
+ return "", "", err
+ }
+ }
+ bts, err := te.Contents()
+ if err != nil {
+ return "", "", err
+ }
+ return string(bts), fp, nil
+ }
+ }
+ return "", "", ErrFileNotFound
+}
@@ -18,7 +18,6 @@ require (
)
require (
- github.com/aymanbagabas/go-osc52 v1.2.2
github.com/caarlos0/env/v7 v7.1.0
github.com/charmbracelet/keygen v0.4.2
github.com/charmbracelet/log v0.2.1
@@ -49,9 +49,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
+github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
-github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo=
-github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -40,7 +40,7 @@ func (d *SqliteBackend) reposPath() string {
// NewSqliteBackend creates a new SqliteBackend.
func NewSqliteBackend(ctx context.Context, cfg *config.Config) (*SqliteBackend, error) {
dataPath := cfg.DataPath
- if err := os.MkdirAll(dataPath, 0755); err != nil {
+ if err := os.MkdirAll(dataPath, os.ModePerm); err != nil {
return nil, err
}
@@ -254,7 +254,7 @@ func (d *SqliteBackend) RenameRepository(oldName string, newName string) error {
}
// Make sure the new repository parent directory exists.
- if err := os.MkdirAll(filepath.Dir(np), 0755); err != nil {
+ if err := os.MkdirAll(filepath.Dir(np), os.ModePerm); err != nil {
return err
}
@@ -639,7 +639,7 @@ func (d *SqliteBackend) InitializeHooks(repo string) error {
repo = utils.SanitizeRepo(repo) + ".git"
hooksPath := filepath.Join(d.reposPath(), repo, "hooks")
- if err := os.MkdirAll(hooksPath, 0755); err != nil {
+ if err := os.MkdirAll(hooksPath, os.ModePerm); err != nil {
return err
}
@@ -659,13 +659,13 @@ func (d *SqliteBackend) InitializeHooks(repo string) error {
var data bytes.Buffer
var args string
hp := filepath.Join(hooksPath, hook)
- if err := os.WriteFile(hp, []byte(hookTpls[i]), 0755); err != nil {
+ if err := os.WriteFile(hp, []byte(hookTpls[i]), os.ModePerm); err != nil {
return err
}
// Create hook.d directory.
hp += ".d"
- if err := os.MkdirAll(hp, 0755); err != nil {
+ if err := os.MkdirAll(hp, os.ModePerm); err != nil {
return err
}
@@ -694,7 +694,7 @@ func (d *SqliteBackend) InitializeHooks(repo string) error {
}
hp = filepath.Join(hp, "soft-serve")
- err = os.WriteFile(hp, data.Bytes(), 0755) //nolint:gosec
+ err = os.WriteFile(hp, data.Bytes(), os.ModePerm) //nolint:gosec
if err != nil {
logger.Error("failed to write hook", "err", err)
continue
@@ -1,59 +1,17 @@
package backend
import (
- "path/filepath"
-
"github.com/charmbracelet/soft-serve/git"
- "github.com/gobwas/glob"
)
// LatestFile returns the contents of the latest file at the specified path in
// the repository and its file path.
func LatestFile(r Repository, pattern string) (string, string, error) {
- g := glob.MustCompile(pattern)
- dir := filepath.Dir(pattern)
repo, err := r.Open()
if err != nil {
return "", "", err
}
- head, err := repo.HEAD()
- if err != nil {
- return "", "", err
- }
- t, err := repo.TreePath(head, dir)
- if err != nil {
- return "", "", err
- }
- ents, err := t.Entries()
- if err != nil {
- return "", "", err
- }
- for _, e := range ents {
- te := e
- fp := filepath.Join(dir, te.Name())
- if te.IsTree() {
- continue
- }
- if g.Match(fp) {
- if te.IsSymlink() {
- bts, err := te.Contents()
- if err != nil {
- return "", "", err
- }
- fp = string(bts)
- te, err = t.TreeEntry(fp)
- if err != nil {
- return "", "", err
- }
- }
- bts, err := te.Contents()
- if err != nil {
- return "", "", err
- }
- return string(bts), fp, nil
- }
- }
- return "", "", git.ErrFileNotFound
+ return git.LatestFile(repo, pattern)
}
// Readme returns the repository's README.
@@ -203,10 +203,10 @@ func ParseConfig(path string) (*Config, error) {
// WriteConfig writes the configuration to the given file.
func WriteConfig(path string, cfg *Config) error {
- if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
+ if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
}
- return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
+ return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o644) // nolint: errcheck
}
// DefaultConfig returns a Config with the values populated with the defaults
@@ -34,7 +34,7 @@ func TestMergeInitAdminKeys(t *testing.T) {
})
is.NoErr(err)
fp := filepath.Join(t.TempDir(), "config.yaml")
- err = os.WriteFile(fp, bts, 0644)
+ err = os.WriteFile(fp, bts, 0o644)
is.NoErr(err)
cfg, err := ParseConfig(fp)
is.NoErr(err)
@@ -55,7 +55,7 @@ func TestValidateInitAdminKeys(t *testing.T) {
})
is.NoErr(err)
fp := filepath.Join(t.TempDir(), "config.yaml")
- err = os.WriteFile(fp, bts, 0644)
+ err = os.WriteFile(fp, bts, 0o644)
is.NoErr(err)
cfg, err := ParseConfig(fp)
is.NoErr(err)