parent
12f3b66b2b
commit
e22f2e6bdb
@ -1,3 +1,10 @@
|
|||||||
module dev.brepo.ru/brepo/hestiacp-php-selector
|
module dev.brepo.ru/brepo/hestiacp-php-selector
|
||||||
|
|
||||||
go 1.22.6
|
go 1.22.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/akamensky/argparse v1.4.0
|
||||||
|
github.com/gofrs/flock v0.12.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.22.0 // indirect
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
|
||||||
|
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||||
|
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||||
|
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
@ -1,8 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
echo "Try to find php-selector for hestiacp"
|
uid=$(id -u)
|
||||||
update-alternatives --display php | grep hestiacp-php-selector
|
if [ "$uid" != "0" ]; then
|
||||||
if [ $? -ne 0 ]; then
|
echo "Command must be executed as privileged user"
|
||||||
echo "Register php-selector"
|
exit 0
|
||||||
update-alternatives --install /usr/bin/php php /usr/bin/hestiacp-php-selector 1
|
fi
|
||||||
fi
|
case "$1" in
|
||||||
|
install)
|
||||||
|
echo "Try to find php-selector for hestiacp"
|
||||||
|
update-alternatives --display php | grep hestiacp-php-selector
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Register php-selector"
|
||||||
|
update-alternatives --install /usr/bin/php php /usr/bin/hestiacp-php-selector 1
|
||||||
|
if [ ! -e /etc/hestia_php_selector/system/php.path ]; then
|
||||||
|
mkdir -p /etc/hestia_php_selector/system/
|
||||||
|
current_path_to_php=$(readlink -f /usr/bin/php)
|
||||||
|
echo "$current_path_to_php" > /etc/hestia_php_selector/system/php.path
|
||||||
|
chmod 644 /etc/hestia_php_selector/system/php.path
|
||||||
|
fi
|
||||||
|
/usr/bin/hestiacp-php-admin add
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
delete)
|
||||||
|
php_sys=$(cat /etc/hestia_php_selector/system/php.path)
|
||||||
|
if [ -n "$php_sys" ]; then
|
||||||
|
update-alternatives --set php "$php_sys"
|
||||||
|
fi
|
||||||
|
/usr/bin/hestiacp-php-admin remove-all
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command"
|
||||||
|
;;
|
||||||
|
esac
|
@ -0,0 +1,318 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/akamensky/argparse"
|
||||||
|
flk "github.com/gofrs/flock"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PATH_TO_CONFIG_NAME = "/php.path"
|
||||||
|
PATH_TO_USER_CONFIG_ROOT = "/etc/hestia_php_selector/%s"
|
||||||
|
PATH_TO_USER_CONFIG = "/usr/local/hestia/data/users/*/user.conf"
|
||||||
|
PATH_TO_USER_CONFIG_FMT = "/usr/local/hestia/data/users/%s/user.conf"
|
||||||
|
PATH_TO_USER_CONFIG_FULL = PATH_TO_USER_CONFIG_ROOT + PATH_TO_CONFIG_NAME
|
||||||
|
PATH_TO_LOCAL_PHP_ROOT = "php_sel"
|
||||||
|
PATH_TO_LOCAL_PHP = PATH_TO_LOCAL_PHP_ROOT + PATH_TO_CONFIG_NAME
|
||||||
|
LOCK_PATH = "lock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func escapeUserName(username string) string {
|
||||||
|
username0 := strings.TrimSpace(username)
|
||||||
|
uname := strings.Split(username0, "/")
|
||||||
|
uname2 := uname[len(uname)-1]
|
||||||
|
if uname2 == "." || uname2 == ".." {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return uname2
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExecOther(mode os.FileMode) bool {
|
||||||
|
return mode&0001 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPHPVerFromConf(path string) (string, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("user config reading error %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
formatedLine := strings.SplitN(strings.TrimSpace(line), "=", 2)
|
||||||
|
if len(formatedLine) < 2 {
|
||||||
|
return "", fmt.Errorf("incorrect string formatting in config %s", line)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(formatedLine[0]) == "PHPCLI" {
|
||||||
|
result := strings.Trim(formatedLine[1], "' ")
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return "", fmt.Errorf("user config reading error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserPHPVerHestia(username string) (string, string, error) {
|
||||||
|
pathToConf := fmt.Sprintf(PATH_TO_USER_CONFIG_FMT, username)
|
||||||
|
phpVer, err := getPHPVerFromConf(pathToConf)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if phpVer == "" {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
phpPath := "/usr/bin/php" + phpVer
|
||||||
|
if finfo, err := os.Stat(phpPath); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
} else {
|
||||||
|
fmode := finfo.Mode()
|
||||||
|
if !isExecOther(fmode) {
|
||||||
|
return "", "", fmt.Errorf("not executable by others %s", phpPath)
|
||||||
|
}
|
||||||
|
if strings.Contains(finfo.Name(), "hestiacp-php-selector") {
|
||||||
|
return "", "", fmt.Errorf("infinite symbolic link")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return phpPath, phpVer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUserPHPVer(username string, php_ver string) error {
|
||||||
|
reIsVer := regexp.MustCompile(`^(\d\.?\d+)`)
|
||||||
|
matches := reIsVer.FindStringSubmatch(php_ver)
|
||||||
|
if len(matches) > 1 {
|
||||||
|
phpver := strings.TrimSpace(matches[1])
|
||||||
|
phpPath := "/usr/bin/php" + phpver
|
||||||
|
if finfo, err := os.Stat(phpPath); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmode := finfo.Mode()
|
||||||
|
if !isExecOther(fmode) {
|
||||||
|
return fmt.Errorf("not executable by others %s", phpPath)
|
||||||
|
}
|
||||||
|
if strings.Contains(finfo.Name(), "hestiacp-php-selector") {
|
||||||
|
return fmt.Errorf("infinite symbolic link")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setUserPhpPathVer(username, phpver, phpPath)
|
||||||
|
} else {
|
||||||
|
reIsVer := regexp.MustCompile(`(\d\.?\d+)$`)
|
||||||
|
matches := reIsVer.FindStringSubmatch(php_ver)
|
||||||
|
vers := ""
|
||||||
|
if len(matches) > 1 {
|
||||||
|
vers = strings.TrimSpace(matches[1])
|
||||||
|
}
|
||||||
|
return setUserPhpPathVer(username, vers, php_ver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFileExists(path string) (bool, bool, error) {
|
||||||
|
if fi, err := os.Stat(path); err == nil {
|
||||||
|
return true, fi.IsDir(), nil
|
||||||
|
} else if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, false, nil
|
||||||
|
} else {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUserPhpPathVer(username string, php_ver string, php_path string) error {
|
||||||
|
un := escapeUserName(username)
|
||||||
|
if un == "" {
|
||||||
|
return fmt.Errorf("incorrect username %s", username)
|
||||||
|
}
|
||||||
|
var root_path string
|
||||||
|
var uid int = -1
|
||||||
|
if un == "root" {
|
||||||
|
root_path = fmt.Sprintf(PATH_TO_USER_CONFIG_ROOT, "system")
|
||||||
|
} else {
|
||||||
|
user_found, err := user.Lookup(un)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
root_path = path.Join(user_found.HomeDir, PATH_TO_LOCAL_PHP_ROOT)
|
||||||
|
Uid_conv, _ := strconv.ParseInt(user_found.Uid, 10, 64)
|
||||||
|
uid = int(Uid_conv)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, isdir, err := isFileExists(root_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
if !isdir {
|
||||||
|
err := os.RemoveAll(root_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Mkdir(root_path, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := os.Mkdir(root_path, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path_to_php_config := path.Join(root_path, PATH_TO_CONFIG_NAME)
|
||||||
|
f, err := os.OpenFile(path_to_php_config, os.O_WRONLY, 0400)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.WriteString(php_path)
|
||||||
|
f.Close()
|
||||||
|
return os.Chown(path_to_php_config, uid, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeUserInfo(username string) error {
|
||||||
|
un := escapeUserName(username)
|
||||||
|
if un == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if un == "root" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
user_found, err := user.Lookup(un)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
root_path := path.Join(user_found.HomeDir, PATH_TO_LOCAL_PHP_ROOT)
|
||||||
|
return os.RemoveAll(root_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func inner_main() int {
|
||||||
|
parser := argparse.NewParser("php-selector", "Set version php cli for user")
|
||||||
|
vConvertExisting := parser.NewCommand("add", "Add existing users to php-selector")
|
||||||
|
vSetPhp := parser.NewCommand("set", "Set php for user: set username 74 or set username /usr/bin/php74")
|
||||||
|
vSetPhp_username := vSetPhp.StringPositional(&argparse.Options{Required: true, Help: "username"})
|
||||||
|
vSetPhp_ver := vSetPhp.StringPositional(&argparse.Options{Required: true, Help: "path to php or php version"})
|
||||||
|
vSetSystem := parser.NewCommand("system", "Set system php version")
|
||||||
|
vSetSystem_ver := vSetSystem.StringPositional(&argparse.Options{Required: true, Help: "path to php or php version"})
|
||||||
|
vRemoveUser := parser.NewCommand("remove", "remove php info for user (reset to system)")
|
||||||
|
vRemoveUser_username := vRemoveUser.StringPositional(&argparse.Options{Required: true, Help: "username to delete"})
|
||||||
|
vRemoveUserAll := parser.NewCommand("remove-all", "remove php info for all users")
|
||||||
|
err := parser.Parse(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, parser.Usage(err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
lock_file := path.Join(fmt.Sprintf(PATH_TO_USER_CONFIG_ROOT, "system"), LOCK_PATH)
|
||||||
|
lock := flk.New(lock_file)
|
||||||
|
err = lock.Lock()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error locking %s\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
defer lock.Close()
|
||||||
|
|
||||||
|
if vConvertExisting.Happened() {
|
||||||
|
config_path, err := filepath.Glob(PATH_TO_USER_CONFIG)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error reading of hestia configs %s\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
for _, v := range config_path {
|
||||||
|
reUserName := regexp.MustCompile(`^/usr/local/hestia/data/users/(.+)/user.conf`)
|
||||||
|
matches := reUserName.FindStringSubmatch(v)
|
||||||
|
if len(matches) > 1 {
|
||||||
|
uName := strings.TrimSpace(matches[1])
|
||||||
|
php_path, php_ver, err := getUserPHPVerHestia(uName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = setUserPhpPathVer(uName, php_ver, php_path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if vSetPhp.Happened() {
|
||||||
|
username := strings.TrimSpace(*vSetPhp_username)
|
||||||
|
php_ver := strings.TrimSpace(*vSetPhp_ver)
|
||||||
|
if username == "" || php_ver == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Username or php version shouldn't be empty")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
err := setUserPHPVer(username, php_ver)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} else if vSetSystem.Happened() {
|
||||||
|
php_ver := strings.TrimSpace(*vSetSystem_ver)
|
||||||
|
if php_ver == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Username or php version shouldn't be empty")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
err := setUserPHPVer("root", php_ver)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} else if vRemoveUser.Happened() {
|
||||||
|
username := strings.TrimSpace(*vRemoveUser_username)
|
||||||
|
if username == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Username shouldn't be empty")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
err := removeUserInfo(username)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} else if vRemoveUserAll.Happened() {
|
||||||
|
config_path, err := filepath.Glob(PATH_TO_USER_CONFIG)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error reading of hestia configs %s\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
for _, v := range config_path {
|
||||||
|
reUserName := regexp.MustCompile(`^/usr/local/hestia/data/users/(.+)/user.conf`)
|
||||||
|
matches := reUserName.FindStringSubmatch(v)
|
||||||
|
if len(matches) > 1 {
|
||||||
|
uName := strings.TrimSpace(matches[1])
|
||||||
|
err := removeUserInfo(uName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "User information error %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if user.Username != "root" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Program should start under privileged user")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(inner_main())
|
||||||
|
}
|
Loading…
Reference in new issue