mirror of
https://github.com/p1neappleXpress/OpenFlux.git
synced 2026-10-02 05:04:39 +08:00
phphost: find the site's folder below the login folder (any hosting)
domains/<site>/public_html, www/<site>, sites/<site>/htdocs...: one is taken, several are offered with full paths. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
a327d0164d
commit
0f9babd5f0
+2
-1
@@ -51,7 +51,8 @@ All notable changes to the OpenFlux core. Format loosely follows
|
||||
8 MB downloads run, and a 4 MB upload takes 33 s instead of timing out.
|
||||
|
||||
- Own node without a server: `provision/phphost` puts the PHP exit on any web
|
||||
host over FTP and checks that it runs. `probe` finds the web folder and
|
||||
host over FTP and checks that it runs. `probe` finds the web folder (also
|
||||
a level or two down: `domains/<site>/public_html`, `www/<site>`) and
|
||||
whether it is writable, `deploy` uploads the bundle embedded in the core
|
||||
(`deploy/phpbox`, with the link parser as WebAssembly), keeps the token of an
|
||||
earlier install and checks file sizes, `check` asks the site (passing the
|
||||
|
||||
@@ -222,7 +222,22 @@ func probe(s *session, t FTP) (*Probe, error) {
|
||||
p.Dir, found = "", true // the login folder is served
|
||||
}
|
||||
if !found {
|
||||
return p, fail(CodeNoWebRoot, strings.Join(dirs, ","), "phphost: cannot tell which folder the site is served from", nil)
|
||||
// Other hosts keep the site a level or two down: domains/example.com/public_html,
|
||||
// www/example.com, sites/example.com/htdocs. One such folder is taken as the answer;
|
||||
// several are offered, with their paths, for the user to choose.
|
||||
deeper := findDeeper(s, dirs)
|
||||
p.Candidates = deeper
|
||||
if len(deeper) == 1 {
|
||||
p.Dir, found = deeper[0], true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
cands := p.Candidates
|
||||
if len(cands) == 0 {
|
||||
cands = dirs
|
||||
}
|
||||
p.Candidates = cands
|
||||
return p, fail(CodeNoWebRoot, strings.Join(cands, ","), "phphost: cannot tell which folder the site is served from", nil)
|
||||
}
|
||||
}
|
||||
// An earlier install: keep its token.
|
||||
@@ -243,6 +258,48 @@ func probe(s *session, t FTP) (*Probe, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// findDeeper looks one and two levels below the login folder for folders that
|
||||
// look like a site's web root: top/x where x is a usual web root name, and
|
||||
// top/x/root for a top folder that holds several sites (domains, sites, www, ...).
|
||||
func findDeeper(s *session, top []string) []string {
|
||||
isRoot := map[string]bool{}
|
||||
for _, n := range webRootNames {
|
||||
isRoot[n] = true
|
||||
}
|
||||
var found []string
|
||||
limit := 0
|
||||
for _, d := range top {
|
||||
if limit++; limit > 16 || strings.HasPrefix(d, ".") {
|
||||
continue
|
||||
}
|
||||
entries, err := s.c.List(d)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
if e.Type != ftp.EntryTypeFolder || e.Name == "." || e.Name == ".." {
|
||||
continue
|
||||
}
|
||||
if isRoot[e.Name] {
|
||||
found = append(found, path.Join(d, e.Name))
|
||||
continue
|
||||
}
|
||||
// a site folder (example.com) holding its own web root
|
||||
sub, err := s.c.List(path.Join(d, e.Name))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, x := range sub {
|
||||
if x.Type == ftp.EntryTypeFolder && isRoot[x.Name] {
|
||||
found = append(found, path.Join(d, e.Name, x.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(found)
|
||||
return found
|
||||
}
|
||||
|
||||
func join(dir, name string) string {
|
||||
if dir == "" {
|
||||
return name
|
||||
|
||||
@@ -180,3 +180,42 @@ func TestRemoveDeletesOnlyWhatWasUploaded(t *testing.T) {
|
||||
t.Error("the user's own file was touched")
|
||||
}
|
||||
}
|
||||
|
||||
// Other hosts keep the site deeper than the login folder.
|
||||
func TestProbeFindsAWebRootBelowTheLoginFolder(t *testing.T) {
|
||||
// domains/example.com/public_html: one site, taken as the answer.
|
||||
one := newFakeFTP(t, "u", "pw", "domains", "domains/example.com", "domains/example.com/public_html", "logs")
|
||||
p, err := ProbeHost(ctx(t), one.target())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p.Dir != "domains/example.com/public_html" || !p.Writable {
|
||||
t.Errorf("probe = %+v", p)
|
||||
}
|
||||
// It deploys there.
|
||||
if _, err := Deploy(ctx(t), one.target(), "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := one.files["/domains/example.com/public_html/config.php"]; !ok {
|
||||
t.Error("the node was not uploaded into the site's folder")
|
||||
}
|
||||
|
||||
// Two sites: the user chooses, and the choices carry the full paths.
|
||||
two := newFakeFTP(t, "u", "pw", "domains", "domains/a.com", "domains/a.com/public_html", "domains/b.org", "domains/b.org/public_html")
|
||||
_, err = ProbeHost(ctx(t), two.target())
|
||||
var e *Error
|
||||
if !errors.As(err, &e) || e.Code != CodeNoWebRoot || e.Param != "domains/a.com/public_html,domains/b.org/public_html" {
|
||||
t.Fatalf("two sites: %v", err)
|
||||
}
|
||||
chosen := two.target()
|
||||
chosen.Dir = "domains/b.org/public_html"
|
||||
if p, err := ProbeHost(ctx(t), chosen); err != nil || p.Dir != "domains/b.org/public_html" || !p.Writable {
|
||||
t.Errorf("chosen: %+v %v", p, err)
|
||||
}
|
||||
|
||||
// www/<site>: a usual name one level down.
|
||||
www := newFakeFTP(t, "u", "pw", "sites", "sites/shop", "sites/shop/www")
|
||||
if p, err := ProbeHost(ctx(t), www.target()); err != nil || p.Dir != "sites/shop/www" {
|
||||
t.Errorf("sites/shop/www: %+v %v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user