Работа над файлом reamde, правки кода, исправление конфигурационного файла repko
234
Readme.md
|
|
@ -99,13 +99,231 @@ used_protocol={{if .is_secure}}https:\\{{else}}http:\\{{end}}
|
|||
## Окна
|
||||
В приложении прописаны несколько типов окон, также приложение максимально адаптировано под малый размер окна терминала. Минимальный размер окна терминала рассчитан на 60*20, при значениях меньше контент обрезается. Добавлен функционал переноса строк и прокручивание контента.
|
||||
### Окно листа
|
||||
Лист это первое окно, которое появляется при запуске. Лист нужен для выбора, какое приложение нужно будет настроить. Окно делится на 2 части, список приложений слева и короткое описание приложения справа. Переключаться между пунктами можно на стрелки вниз и вверх. При переключении автоматически меняется описание в блоке справа.
|
||||

|
||||
Лист это первое окно, которое появляется при запуске. Лист нужен для выбора, какое приложение нужно будет настроить. Окно делится на 2 части, список приложений слева и короткое описание приложения справа. Переключаться между пунктами можно на стрелки вниз и вверх. При переключении автоматически меняется описание в блоке справа.
|
||||

|
||||
Если приложений в списке слишком много, то они автоматически переносятся на следующую страницу, с отображением текущей страницы. Для быстрого переключения между страницами можно использовать стрелки влево и вправо.
|
||||

|
||||

|
||||

|
||||
Для выбора приложения, используется клавиша enter, при её нажатии. Появляется модальное окно, где нужно подтвердить выбор приложения для настройки
|
||||

|
||||

|
||||

|
||||

|
||||
Для выбора приложения, используется клавиша enter, при её нажатии. Появляется [модальное окно](Readme.md#модальное-окно-подтверждения-выбора-настраиваемого-приложения) подтверждения выбора настраиваемого приложения, где нужно подтвердить выбор приложения для настройки
|
||||
|
||||
### Окно секции/Формы
|
||||
### Окно секции
|
||||
Секция это блок посвященный конкретному разделу настройки приложений. В секции может находиться форма с полями, более подробное описание приложения или таблица переменных и их значений.
|
||||
У секции могут быть условия отображения, что позволяет пропустить или задействовать определенные этапы настройки в зависимости от значений переменных или данных в системе.
|
||||
Секция About предназначена для вывода подробной информации о проекте. По задумке эта секция должна идти первой, но если прописать её в других местах или несколько раз, система не выдаст ошибку. Если Разработчик сочтет нужным, он может добавлять About перед каждой секцией формы, для подробного объяснения каждого поля.
|
||||

|
||||
При переходе на следующую секцию, если у текущей секции прописаны действия запускается поток. В нем последовательно обрабатывается каждое действие, чье условие выполняется. Во время обработки появляется [модальное окно](Readme.md#модальное-окно-ожидания-выполнения-действий) ожидания выполненения действий.
|
||||
Когда обработка закончится, открывается либо следующая секция, либо если действие завершилось с ошибкой, показывается [модальное окно](Readme.md#модальное-окно-предупреждения-об-ошибке) предупреждения об ошибке, а после окно логирования действий.
|
||||
|
||||
Секция Формы предназначена для предоставления различных полей для значений настройки приложения. Это могут быть простые поля ввода данных, раскрывающийся список для выбора из определенных опций, чекбокс для подтверждения или список выбора файла или директории. Каждое поле также может использовать условия для отображения.
|
||||

|
||||

|
||||
Каждое поле также поддерживает валидацию. Что позволяет заранее узнать о возможных ошибках при настройке. Важно, в текущей версии GoSetup ошибка валидации не запрещает использовать введенное значение.
|
||||

|
||||
|
||||
Секция Final предназначена для вывода вводимых данных после настройки. В этот момент пользователь может проверить все ли вводимые им данные верны.
|
||||

|
||||

|
||||
После нажатия клавиши enter запускаются последние действия, если они выполнились успешно, то появится [модальное окно](Readme.md#модальное-окно-завершения-настройки) завершения настройки. При его нажатии настройка завершается и приложения закрывается, возвращая статус код 0.
|
||||
|
||||
### Модальные окна
|
||||
Модальны окна предназначены для вывода сообщений на экран и получения подтверждений действий пользователя. Есть следующие модальные окна.
|
||||
#### Модальное окно подтверждения выбора настраиваемого приложения
|
||||

|
||||
#### Модальное окно подтверждения возврата к списку приложений
|
||||

|
||||
#### Модальное окно подтверждения выхода
|
||||

|
||||
#### Модальное окно завершения настройки
|
||||

|
||||
#### Модальное окно ожидания выполнения действий
|
||||

|
||||
#### Модальное окно предупреждения об ошибке
|
||||

|
||||
|
||||
### Окно логирования
|
||||
Для просмотра результатов выполняемых действий секции, предусмотрено окно Логов, куда по мере выполнения действия добавляются сообщения результаты. Окно открывается автоматически если во время выполнения произошла ошибка, также есть горячая клавиша ["ctrl+l"](Readme.md#горячие-клавиши), что скрывает или отображает окно.
|
||||

|
||||

|
||||

|
||||

|
||||
В случае когда действие завершается ошибкой, то сообщение выглядит следующим образом.
|
||||

|
||||
|
||||
## Горячие клавиши
|
||||
Для работы с приложением заранее определен ряд клавиш:
|
||||
- Клавиши движения. Эти клавиши предназначены для смены фокуса между элементами. К ним относятся
|
||||
- up, shift+tab - перемещают фокус выше, на прошлый элемент
|
||||
- down, tab - перемещают фокус ниже, на следующий элемент
|
||||
- left - перемещают фокус левее
|
||||
- right - перемещают фокус правее
|
||||
- home - прокрутка к старту области
|
||||
- end - прокрутка к окончанию области
|
||||
- Клавиши взаимодействия. Эти клавиши отвечают за работу непосредственно с процессом настройки. К ним относятся
|
||||
- enter - подтверждает действие, например открытие поля, перехода на следующую секцию или модального окна
|
||||
- ctrl+l - открывает или закрывает окно логирования
|
||||
- esc - закрывает открытое поля списка, если фокус не был на открытом поле списка, то появляется [модальное окно](Readme.md#модальное-окно-подтверждения-возврата-к-списку-приложений) подтверждения возврата к списку приложений
|
||||
- ctrl+c, ctrl+q - открывает [модальное окно](Readme.md#модальное-окно-подтверждения-выхода) подтверждения выхода
|
||||
|
||||
## Дополнительно, Важное
|
||||
В текущей итерации не учтен ряд элементов. Это могут быть вопросы реализации, правил поведения приложения, удобство пользования.
|
||||
|
||||
## Собственные функции для expr
|
||||
Библиотека [expr](https://github.com/expr-lang/expr) имеет ряд готовых функций их можно просмотреть [здесь](https://github.com/expr-lang/expr/blob/master/builtin/builtin.go). Также для были определенны собственные функции, которые можно посмотреть в файле [здесь](setup/utils/utils.go#L153)
|
||||
```go
|
||||
// DefineExprCustomFunctions возвращает собственные функции expr
|
||||
func DefineExprCustomFunctions() []expr.Option {
|
||||
return []expr.Option{
|
||||
//вычисляет значение по заданному регулярному выражению
|
||||
expr.Function("regex",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 3 {
|
||||
return "", fmt.Errorf("regex() ожидает 3 аргумента")
|
||||
}
|
||||
input := fmt.Sprintf("%v", args[0])
|
||||
pattern := fmt.Sprintf("%v", args[1])
|
||||
repl := fmt.Sprintf("%v", args[2])
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return input, nil
|
||||
}
|
||||
return re.ReplaceAllString(input, repl), nil
|
||||
}, new(func(string, string, string) string),
|
||||
),
|
||||
//проверяет совпадение переданного значения и указанного регулярного выражения
|
||||
expr.Function("regex_match",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf("regex_match() ожидает 2 аргумента")
|
||||
}
|
||||
input := fmt.Sprintf("%v", args[0])
|
||||
pattern := fmt.Sprintf("%v", args[1])
|
||||
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("некорректный regex %s, err:%v", pattern, err)
|
||||
}
|
||||
return re.MatchString(input), nil
|
||||
|
||||
}, new(func(string, string) bool),
|
||||
),
|
||||
//возвращает собственный текст ошибки
|
||||
expr.Function("error",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("error() требует сообщения")
|
||||
}
|
||||
return nil, fmt.Errorf("%v", args[0])
|
||||
},
|
||||
new(func(string) error),
|
||||
),
|
||||
//возвращает тело ответа по указанному адресу
|
||||
expr.Function("http_get",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
url := fmt.Sprintf("%v", args[0])
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return string(body), nil
|
||||
},
|
||||
new(func(string) string),
|
||||
),
|
||||
//возвращает результат выполнения команды из оболочки
|
||||
expr.Function("shell",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
cmd := fmt.Sprintf("%v", args[0])
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
out, err := exec.Command(parts[0], parts[1:]...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
},
|
||||
new(func(string) error),
|
||||
),
|
||||
//возвращает значение указанной переменной из env
|
||||
expr.Function("env",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return os.Getenv(fmt.Sprintf("%v", args[0])), nil
|
||||
}, new(func(string) error),
|
||||
),
|
||||
//проверяет наличие файла в системе
|
||||
expr.Function("file_exists",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
path := fmt.Sprintf("%v", args[0])
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err == nil && !fileInfo.IsDir() {
|
||||
return true, nil
|
||||
} else if err == nil && fileInfo.IsDir() {
|
||||
return false, fmt.Errorf("file is dir")
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}, new(func(string) bool),
|
||||
),
|
||||
//проверяет наличие директории в системе
|
||||
expr.Function("dir_exists",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
path := fmt.Sprintf("%v", args[0])
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return true, nil
|
||||
} else if err == nil && !fileInfo.IsDir() {
|
||||
return false, fmt.Errorf("file is not a dir")
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}, new(func(string) bool),
|
||||
),
|
||||
//проверяет доступность порта
|
||||
expr.Function("port_free",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
port := fmt.Sprintf("%v", args[0])
|
||||
cmd := exec.Command("ss", "-ltn", "sport =: "+port)
|
||||
return cmd.Run() != nil, nil
|
||||
}, new(func(string) error),
|
||||
),
|
||||
//проверяет наличие пользователя в системе
|
||||
expr.Function("user_exists",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
name := fmt.Sprintf("%v", args[0])
|
||||
cmd := exec.Command("id", "-u", name)
|
||||
return cmd.Run() == nil, nil
|
||||
}, new(func(string) bool),
|
||||
),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ variables:
|
|||
default: "Repko Repository"
|
||||
- name: "gpg_note"
|
||||
type: "string"
|
||||
default: "noe"
|
||||
default: "note"
|
||||
- name: "gpg_mail"
|
||||
type: "string"
|
||||
default: "admin@repko.com"
|
||||
|
|
@ -105,11 +105,14 @@ sections:
|
|||
type: "about"
|
||||
description: "Описание приложения"
|
||||
paragraphs:
|
||||
- "# Repko - собственный сервер репозитория для debian систем. "
|
||||
- "## Возможности: \n - Встроенная система аутентификации (локальные пользователи или LDAP).\n - Подпись gpg ключом.\n - Удобный веб интерфейс"
|
||||
- "## Бла бла бла. \n lorem ipsum прочее прочее прочее прочее"
|
||||
- "### Бла бла бла бла. \n lorem ipsum прочее прочее прочее прочее"
|
||||
- "## Бла бла бла бла. \n lorem ipsum прочее прочее прочее прочее"
|
||||
- "# Repko - инновационное решение для создания и управления репозиториями Debian на собственном хостинге."
|
||||
- "Разработан на языке GO,обеспечивает высокую производительность и надежность, подходит как для небольших проектов, так и для корпоративных сред."
|
||||
- "## Ключевые особенности:"
|
||||
- "- Гибкость хранения метаданных: поддержка PostgreSQl и SQLite"
|
||||
- "- Автоматическая генерация debian структуры"
|
||||
- "- Автоматическая подпись файлов Release"
|
||||
- "- Высокая производительность и минимальное потребление ресурсов"
|
||||
- "- Современный веб-интерфейс на Next.js"
|
||||
actions:
|
||||
- name: "Create directories"
|
||||
type: "command"
|
||||
|
|
@ -155,7 +158,7 @@ sections:
|
|||
actions:
|
||||
- name: "create_user"
|
||||
type: "command"
|
||||
command: "echo 'adduser --system --group --no-create-home --disabled-password --shell /bin/false {{.repko_user}}'"
|
||||
command: "adduser --system --group --no-create-home --disabled-password --shell /bin/false {{.repko_user}}"
|
||||
condition: "user_type=='Create'"
|
||||
run-as: "root"
|
||||
require-packages:
|
||||
|
|
@ -177,18 +180,6 @@ sections:
|
|||
type: "command"
|
||||
command: "chmod -R 700 /var/lib/repko/gnupg"
|
||||
error: "Ошибка установки прав 700"
|
||||
- name: "Set files ownership"
|
||||
type: "command"
|
||||
command: "chown {{.repko_user}}:{{.repko_user}} /opt/repko/repko /opt/repko/.env /etc/repko/config.ini"
|
||||
error: "Ошибка установка владельца для файлов"
|
||||
- name: "Set rights 750"
|
||||
type: "command"
|
||||
command: "chmod 750 /opt/repko/repko"
|
||||
error: "Ошибка установки прав 750"
|
||||
- name: "Set rights 640"
|
||||
type: "command"
|
||||
command: "chmod 640 /opt/repko/.env /etc/repko/config.ini"
|
||||
error: "Ошибка установки прав 640"
|
||||
- name: "domain"
|
||||
description: "Настройка используемых доменов"
|
||||
fields:
|
||||
|
|
@ -414,7 +405,7 @@ sections:
|
|||
actions:
|
||||
- name: "generate gpg key"
|
||||
type: "command"
|
||||
command: "sudo -u {{.repko_user}} gpg --homedir /var/lib/repko/gnupg --batch --passphrase '' --quick-generate-key '{.gpg_full_name} ({.gpg_note}) <{.gpg_mail}>' rsa4096"
|
||||
command: "sudo -u {{.repko_user}} gpg --homedir /var/lib/repko/gnupg --batch --passphrase '' --quick-generate-key '{{.gpg_full_name}} ({{.gpg_note}}) <{{.gpg_mail}}>' rsa4096"
|
||||
error: "Не удалось создать ключ"
|
||||
- name: "get gpg key id"
|
||||
type: "command"
|
||||
|
|
@ -423,7 +414,7 @@ sections:
|
|||
assign-to: "gpg_key_id"
|
||||
- name: "export gpg key"
|
||||
type: "command"
|
||||
command: "sudo -u {{.repko_user}} gpg --homedir /var/lib/repko/gnupg --armor --export {{.gpg_key_id}} > /usr/share/keyrings/repko.asc"
|
||||
command: "sudo -u {{.repko_user}} gpg --homedir /var/lib/repko/gnupg --armor --export '{{.gpg_key_id}}!' 2>/dev/null > /usr/share/keyrings/repko.asc"
|
||||
error: "Не удалось экспортировать gpg ключ"
|
||||
- name: "write gpg key id"
|
||||
type: "command"
|
||||
|
|
@ -525,4 +516,16 @@ sections:
|
|||
type: "file_template"
|
||||
file-source: "/opt/repko/donors/config.json"
|
||||
file-target: "/var/www/repko/web/config.json"
|
||||
error: "Ошибка при создании файла repko web config"
|
||||
error: "Ошибка при создании файла repko web config"
|
||||
- name: "Set files ownership"
|
||||
type: "command"
|
||||
command: "chown {{.repko_user}}:{{.repko_user}} /opt/repko/repko /opt/repko/.env /etc/repko/config.ini"
|
||||
error: "Ошибка установка владельца для файлов"
|
||||
- name: "Set rights 750"
|
||||
type: "command"
|
||||
command: "chmod 750 /opt/repko/repko"
|
||||
error: "Ошибка установки прав 750"
|
||||
- name: "Set rights 640"
|
||||
type: "command"
|
||||
command: "chmod 640 /opt/repko/.env /etc/repko/config.ini"
|
||||
error: "Ошибка установки прав 640"
|
||||
BIN
public/modal_back_to_list.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/modal_exit.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/section_about_repko.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
public/section_field_condition1.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/section_field_condition2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/section_field_validation_error.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/section_final1.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/section_final2.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/section_log_action1.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
public/section_log_action2.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/section_log_action3.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/section_log_action4.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/section_log_error.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/section_modal_end.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/section_modal_error_action.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/section_modal_loading.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -438,7 +438,7 @@ func simpleAboutRender(paragraphs []string, width int) string {
|
|||
if i != len(paragraphs)-1 {
|
||||
switch strokeType {
|
||||
case "normal", "quote", "bullet":
|
||||
finalStroke += "\n"
|
||||
finalStroke += "\n\n"
|
||||
case "header1", "header2", "header3":
|
||||
finalStroke += "\n\n"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("некоректный regex %s, err:%v", pattern, err)
|
||||
return false, fmt.Errorf("некорректный regex %s, err:%v", pattern, err)
|
||||
}
|
||||
return re.MatchString(input), nil
|
||||
|
||||
|
|
|
|||