Compare commits
No commits in common. "main" and "v0.1.0-alpha.2" have entirely different histories.
main
...
v0.1.0-alp
|
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -82,8 +81,7 @@ func (s *Server) handleAdminUsers(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
rows, err := s.db.Query("SELECT id, username, email, confirmed, blocked, created_at FROM server_users ORDER BY created_at DESC")
|
||||
if err != nil {
|
||||
log.Printf("admin users: query failed: %v", err)
|
||||
http.Error(w, t(s.locale(), "server.internalError"), http.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
@ -129,8 +127,7 @@ func (s *Server) handleAdminDevices(w http.ResponseWriter, r *http.Request) {
|
|||
rows, err := s.db.Query(`SELECT d.id, d.name, d.client_version, COALESCE(d.last_seen,''), COALESCE(d.revoked_at,''), d.created_at
|
||||
FROM server_devices d ORDER BY d.created_at DESC`)
|
||||
if err != nil {
|
||||
log.Printf("admin devices: query failed: %v", err)
|
||||
http.Error(w, t(s.locale(), "server.internalError"), http.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
@ -217,12 +214,11 @@ func (s *Server) handleAdminSMTPTest(w http.ResponseWriter, r *http.Request) {
|
|||
to = from
|
||||
}
|
||||
if host == "" || port == "" || from == "" {
|
||||
jsonOK(w, map[string]interface{}{"ok": false, "error": t(s.locale(), "admin.smtpRequired")})
|
||||
jsonOK(w, map[string]interface{}{"ok": false, "error": "host, port and from required"})
|
||||
return
|
||||
}
|
||||
if err := s.smtpTest(host, port, user, pass, security, from, to); err != nil {
|
||||
log.Printf("admin SMTP test failed: %v", err)
|
||||
jsonOK(w, map[string]interface{}{"ok": false, "error": t(s.locale(), "admin.smtpTestFailed")})
|
||||
jsonOK(w, map[string]interface{}{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
jsonOK(w, map[string]interface{}{"ok": true})
|
||||
|
|
@ -239,7 +235,7 @@ func (s *Server) handleAdminAPIDevices(w http.ResponseWriter, r *http.Request) {
|
|||
LEFT JOIN server_users u ON u.id = d.user_id
|
||||
ORDER BY d.created_at DESC`)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
@ -269,7 +265,7 @@ func (s *Server) handleAdminAPIKeys(w http.ResponseWriter, r *http.Request) {
|
|||
case "GET":
|
||||
rows, err := s.db.Query("SELECT id, name, api_key FROM server_devices ORDER BY created_at")
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
@ -317,7 +313,7 @@ func (s *Server) handleAdminAPIKeysDelete(w http.ResponseWriter, r *http.Request
|
|||
id := strings.TrimPrefix(r.URL.Path, "/admin/api/keys/")
|
||||
_, err := s.db.Exec("DELETE FROM server_devices WHERE id=?", id)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
s.db.Exec("DELETE FROM server_user_devices WHERE device_id=?", id)
|
||||
|
|
@ -377,7 +373,7 @@ func (s *Server) handleAdminAPIUsers(w http.ResponseWriter, r *http.Request) {
|
|||
args = append(args, perPage, offset)
|
||||
rows, err := s.db.Query(sql, args...)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
@ -437,7 +433,7 @@ func (s *Server) handleAdminAPIUserActions(w http.ResponseWriter, r *http.Reques
|
|||
hash, _ := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost)
|
||||
_, err := s.db.Exec("UPDATE server_users SET password_hash=? WHERE id=?", string(hash), id)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
jsonOK(w, map[string]interface{}{"status": "ok", "new_password": newPass})
|
||||
|
|
@ -460,7 +456,7 @@ func (s *Server) handleAdminAPIUserActions(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
_, err := s.db.Exec("UPDATE server_users SET username=?, email=? WHERE id=?", editReq.Username, strings.ToLower(editReq.Email), id)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
jsonOK(w, map[string]interface{}{"status": "ok"})
|
||||
|
|
@ -538,9 +534,8 @@ func (s *Server) handleAdminCreateUser(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(409)
|
||||
w.Write([]byte(errorPageHTML(locale, t(locale, "common.error"), "Username or email already taken", "/admin/create-user")))
|
||||
} else {
|
||||
log.Printf("admin create user: failed: %v", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errorPageHTML(locale, t(locale, "common.error"), t(locale, "admin.createUserFailed"), "/admin/create-user")))
|
||||
w.Write([]byte(errorPageHTML(locale, t(locale, "common.error"), err.Error(), "/admin/create-user")))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -592,7 +587,7 @@ func (s *Server) handleAdminAPICreateUser(w http.ResponseWriter, r *http.Request
|
|||
if strings.Contains(err.Error(), "UNIQUE") {
|
||||
jsonErr(w, 409, "username or email already taken")
|
||||
} else {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ func (s *Server) handleClientPair(w http.ResponseWriter, r *http.Request) {
|
|||
deviceID, req.DeviceName, hex.EncodeToString(apiKey), tokenHash, prefix, suffix,
|
||||
userID, req.VaultID, req.ClientVersion, ip, now, now)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
s.db.Exec("INSERT OR IGNORE INTO server_user_devices (user_id, device_id) VALUES (?, ?)", userID, deviceID)
|
||||
|
|
@ -335,7 +335,7 @@ func (s *Server) handleDeviceRegister(w http.ResponseWriter, r *http.Request) {
|
|||
deviceID, req.Name, apiKey, userID, req.VaultID, now, now,
|
||||
)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
s.db.Exec("INSERT OR IGNORE INTO server_user_devices (user_id, device_id) VALUES (?, ?)", userID, deviceID)
|
||||
|
|
@ -369,7 +369,7 @@ func (s *Server) handleSyncPush(w http.ResponseWriter, r *http.Request) {
|
|||
} `json:"ops"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonErr(w, 400, "invalid JSON")
|
||||
jsonErr(w, 400, "invalid JSON: "+err.Error())
|
||||
return
|
||||
}
|
||||
if req.IdempotencyKey != "" {
|
||||
|
|
@ -479,7 +479,7 @@ func (s *Server) handleSyncPull(w http.ResponseWriter, r *http.Request) {
|
|||
WHERE user_id=? AND vault_id=? AND server_sequence > ? AND server_sequence IS NOT NULL
|
||||
ORDER BY server_sequence`, scope.UserID, scope.VaultID, req.SinceSequence)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
@ -515,7 +515,7 @@ func (s *Server) handleBlobs(w http.ResponseWriter, r *http.Request) {
|
|||
switch r.Method {
|
||||
case "POST":
|
||||
if err := r.ParseMultipartForm(200 << 20); err != nil {
|
||||
jsonErr(w, 400, "invalid multipart request")
|
||||
jsonErr(w, 400, "multipart error: "+err.Error())
|
||||
return
|
||||
}
|
||||
file, header, err := r.FormFile("file")
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
|||
jsonErr(w, 409, "username or email already taken")
|
||||
return
|
||||
}
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
tok := make([]byte, 24)
|
||||
|
|
|
|||
|
|
@ -78,9 +78,8 @@ func (s *Server) handleUserWebRegister(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(409)
|
||||
w.Write([]byte(errorPageHTML(locale, t(locale, "common.error"), "Username or email already taken", "/register")))
|
||||
} else {
|
||||
log.Printf("register web: create user failed: %v", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errorPageHTML(locale, t(locale, "common.error"), t(locale, "server.registrationFailed"), "/register")))
|
||||
w.Write([]byte(errorPageHTML(locale, t(locale, "common.error"), err.Error(), "/register")))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -353,7 +352,7 @@ func (s *Server) handleUserDevices(w http.ResponseWriter, r *http.Request) {
|
|||
WHERE ud.user_id = ?
|
||||
ORDER BY d.created_at DESC`, userID)
|
||||
if err != nil {
|
||||
jsonInternalError(w, err)
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
@ -19,11 +18,6 @@ func jsonErr(w http.ResponseWriter, code int, msg string) {
|
|||
json.NewEncoder(w).Encode(map[string]string{"error": msg})
|
||||
}
|
||||
|
||||
func jsonInternalError(w http.ResponseWriter, err error) {
|
||||
log.Printf("request failed: %v", err)
|
||||
jsonErr(w, http.StatusInternalServerError, "internal error")
|
||||
}
|
||||
|
||||
func sha256Hex(s string) string {
|
||||
h := sha256.Sum256([]byte(s))
|
||||
return hex.EncodeToString(h[:])
|
||||
|
|
|
|||
|
|
@ -16,263 +16,253 @@ func t(locale, key string) string {
|
|||
|
||||
var _translations = map[string]map[string]string{
|
||||
"ru": {
|
||||
"server.registerTitle": "Регистрация",
|
||||
"server.register": "Регистрация",
|
||||
"server.username": "Имя пользователя",
|
||||
"server.email": "Email",
|
||||
"server.password": "Пароль",
|
||||
"server.registerBtn": "Зарегистрироваться",
|
||||
"server.alreadyHaveAccount": "Уже есть аккаунт?",
|
||||
"server.loginBtn": "Войти",
|
||||
"server.loginTitle": "Вход",
|
||||
"server.usernameOrEmail": "Имя пользователя или email",
|
||||
"server.forgotPassword": "Забыли пароль?",
|
||||
"server.adminLink": "Админ",
|
||||
"server.dashboard": "Панель управления",
|
||||
"server.allFieldsRequired": "Все поля обязательны",
|
||||
"server.back": "Назад",
|
||||
"server.emailConfirmBody": "Подтвердите регистрацию: %s",
|
||||
"server.emailConfirmSubject": "Verstak — подтверждение email",
|
||||
"server.registrationSuccess": "Регистрация успешна",
|
||||
"server.registrationEmailSent": "Письмо с ссылкой подтверждения отправлено.",
|
||||
"server.registrationCheckEmail": "Проверьте почту и подтвердите аккаунт.",
|
||||
"server.registrationAutoMessage": "Email не подтверждается. Токен подтверждения выведен в лог сервера.",
|
||||
"server.resetPasswordTitle": "Сброс пароля",
|
||||
"server.resetPassword": "Сброс пароля",
|
||||
"server.resetInstruction": "Введите email для получения ссылки сброса.",
|
||||
"server.sendLink": "Отправить ссылку",
|
||||
"server.backToLogin": "Вернуться к входу",
|
||||
"server.emailSentTitle": "Письмо отправлено",
|
||||
"server.emailSent": "Письмо отправлено",
|
||||
"server.emailSentMessage": "Если аккаунт существует, ссылка для сброса пароля отправлена на почту.",
|
||||
"server.goHome": "На главную",
|
||||
"server.newPasswordTitle": "Новый пароль",
|
||||
"server.newPassword": "Новый пароль",
|
||||
"server.passwordConfirm": "Подтвердите пароль",
|
||||
"server.save": "Сохранить",
|
||||
"server.passwordChanged": "Пароль изменён",
|
||||
"server.passwordChangedMessage": "Пароль успешно изменён. Теперь можно войти.",
|
||||
"server.emailConfirmed": "Email подтверждён",
|
||||
"server.emailConfirmedMessage": "Аккаунт активирован. Теперь можно войти.",
|
||||
"server.needEmail": "Введите email",
|
||||
"server.passwordsDoNotMatch": "Пароли не совпадают",
|
||||
"server.newPasswordResult": "Новый пароль для %s:\n",
|
||||
"server.logout": "Выйти",
|
||||
"server.error": "Ошибка",
|
||||
"server.internalError": "Что-то пошло не так. Повторите попытку.",
|
||||
"server.registrationFailed": "Не удалось зарегистрировать аккаунт. Повторите попытку.",
|
||||
"userDashboard.devices": "Устройства",
|
||||
"userDashboard.device": "Устройство",
|
||||
"userDashboard.status": "Статус",
|
||||
"userDashboard.connected": "Подключено",
|
||||
"userDashboard.lastSeen": "Последний раз",
|
||||
"userDashboard.version": "Версия",
|
||||
"userDashboard.connectNew": "Подключить новое устройство",
|
||||
"userDashboard.connectNewHint": "Установите Верстак на новом устройстве и выполните регистрацию.",
|
||||
"userDashboard.revokeConfirm": "Отозвать устройство?",
|
||||
"userDashboard.revokePrompt": "Введите пароль для подтверждения:",
|
||||
"userDashboard.noDevices": "Нет устройств",
|
||||
"userDashboard.active": "Активно",
|
||||
"userDashboard.revoked": "Отозвано",
|
||||
"userDashboard.revoke": "Отозвать",
|
||||
"admin.login": "Вход администратора",
|
||||
"admin.username": "Имя пользователя",
|
||||
"admin.password": "Пароль",
|
||||
"admin.loginBtn": "Войти",
|
||||
"admin.dashboard": "Панель управления",
|
||||
"admin.deviceCount": "Устройств",
|
||||
"admin.opsCount": "Операций",
|
||||
"admin.devices": "Устройства",
|
||||
"admin.noDevices": "Нет устройств",
|
||||
"admin.device": "Устройство",
|
||||
"admin.user": "Пользователь",
|
||||
"admin.version": "Версия",
|
||||
"admin.status": "Статус",
|
||||
"admin.lastSeen": "Последний раз",
|
||||
"admin.active": "Активно",
|
||||
"admin.revoked": "Отозвано",
|
||||
"admin.revoke": "Отозвать",
|
||||
"admin.smtp": "SMTP",
|
||||
"admin.users": "Пользователи",
|
||||
"admin.usersHeading": "Пользователи",
|
||||
"admin.healthCheck": "Проверка здоровья",
|
||||
"admin.smtpServer": "SMTP сервер",
|
||||
"admin.smtpPort": "SMTP порт",
|
||||
"admin.smtpType": "Тип шифрования",
|
||||
"admin.smtpNoEncryption": "Без шифрования",
|
||||
"admin.smtpUsername": "Имя пользователя SMTP",
|
||||
"admin.smtpPassword": "Пароль SMTP",
|
||||
"admin.smtpFrom": "Отправитель",
|
||||
"admin.smtpServerURL": "URL сервера",
|
||||
"admin.smtpSave": "Сохранить",
|
||||
"admin.smtpTest": "Тест",
|
||||
"admin.smtpTitle": "Настройки SMTP",
|
||||
"admin.smtpTesting": "Проверка...",
|
||||
"admin.smtpPassed": "✓ Тест пройден",
|
||||
"admin.smtpRequired": "Укажите SMTP-сервер, порт и отправителя.",
|
||||
"admin.smtpTestFailed": "Не удалось проверить настройки SMTP. Повторите попытку.",
|
||||
"admin.revokeConfirm": "Вы уверены?",
|
||||
"common.loading": "Загрузка...",
|
||||
"common.ok": "OK",
|
||||
"common.error": "Ошибка",
|
||||
"admin.filterPlaceholder": "Поиск...",
|
||||
"admin.email": "Email",
|
||||
"admin.actions": "Действия",
|
||||
"admin.confirmTitle": "Подтверждение",
|
||||
"admin.modalCancel": "Отмена",
|
||||
"admin.modalConfirm": "Подтвердить",
|
||||
"admin.editUser": "Редактировать пользователя",
|
||||
"admin.editBtn": "Сохранить",
|
||||
"admin.resultTitle": "Результат",
|
||||
"admin.confirmed": "Подтверждён",
|
||||
"admin.unconfirmed": "Не подтверждён",
|
||||
"admin.blocked": "Заблокирован",
|
||||
"admin.unblock": "Разблокировать",
|
||||
"admin.block": "Заблокировать",
|
||||
"admin.resetPassword": "Сбросить пароль",
|
||||
"admin.noUsers": "Нет пользователей",
|
||||
"admin.resetPasswordConfirm": "Сбросить пароль",
|
||||
"admin.resetPasswordMessage": "Новый пароль: ",
|
||||
"admin.resetBtn": "Сбросить",
|
||||
"admin.deleteUser": "Удалить",
|
||||
"admin.deleteUserMessage": "Удалить пользователя %s?",
|
||||
"admin.deleteBtn": "Удалить",
|
||||
"admin.unblockUserTitle": "Разблокировать",
|
||||
"admin.blockUserTitle": "Заблокировать",
|
||||
"admin.unblockUserMessage": "Разблокировать пользователя?",
|
||||
"admin.blockUserMessage": "Заблокировать пользователя?",
|
||||
"admin.createUser": "Создать пользователя",
|
||||
"admin.createUserBtn": "Создать",
|
||||
"admin.createUserFailed": "Не удалось создать пользователя. Повторите попытку.",
|
||||
"server.registerTitle": "Регистрация",
|
||||
"server.register": "Регистрация",
|
||||
"server.username": "Имя пользователя",
|
||||
"server.email": "Email",
|
||||
"server.password": "Пароль",
|
||||
"server.registerBtn": "Зарегистрироваться",
|
||||
"server.alreadyHaveAccount": "Уже есть аккаунт?",
|
||||
"server.loginBtn": "Войти",
|
||||
"server.loginTitle": "Вход",
|
||||
"server.usernameOrEmail": "Имя пользователя или email",
|
||||
"server.forgotPassword": "Забыли пароль?",
|
||||
"server.adminLink": "Админ",
|
||||
"server.dashboard": "Панель управления",
|
||||
"server.allFieldsRequired": "Все поля обязательны",
|
||||
"server.back": "Назад",
|
||||
"server.emailConfirmBody": "Подтвердите регистрацию: %s",
|
||||
"server.emailConfirmSubject": "Verstak — подтверждение email",
|
||||
"server.registrationSuccess": "Регистрация успешна",
|
||||
"server.registrationEmailSent": "Письмо с ссылкой подтверждения отправлено.",
|
||||
"server.registrationCheckEmail":"Проверьте почту и подтвердите аккаунт.",
|
||||
"server.registrationAutoMessage":"Email не подтверждается. Токен подтверждения выведен в лог сервера.",
|
||||
"server.resetPasswordTitle": "Сброс пароля",
|
||||
"server.resetPassword": "Сброс пароля",
|
||||
"server.resetInstruction": "Введите email для получения ссылки сброса.",
|
||||
"server.sendLink": "Отправить ссылку",
|
||||
"server.backToLogin": "Вернуться к входу",
|
||||
"server.emailSentTitle": "Письмо отправлено",
|
||||
"server.emailSent": "Письмо отправлено",
|
||||
"server.emailSentMessage": "Если аккаунт существует, ссылка для сброса пароля отправлена на почту.",
|
||||
"server.goHome": "На главную",
|
||||
"server.newPasswordTitle": "Новый пароль",
|
||||
"server.newPassword": "Новый пароль",
|
||||
"server.passwordConfirm": "Подтвердите пароль",
|
||||
"server.save": "Сохранить",
|
||||
"server.passwordChanged": "Пароль изменён",
|
||||
"server.passwordChangedMessage":"Пароль успешно изменён. Теперь можно войти.",
|
||||
"server.emailConfirmed": "Email подтверждён",
|
||||
"server.emailConfirmedMessage": "Аккаунт активирован. Теперь можно войти.",
|
||||
"server.needEmail": "Введите email",
|
||||
"server.passwordsDoNotMatch": "Пароли не совпадают",
|
||||
"server.newPasswordResult": "Новый пароль для %s:\n",
|
||||
"server.logout": "Выйти",
|
||||
"server.error": "Ошибка",
|
||||
"userDashboard.devices": "Устройства",
|
||||
"userDashboard.device": "Устройство",
|
||||
"userDashboard.status": "Статус",
|
||||
"userDashboard.connected": "Подключено",
|
||||
"userDashboard.lastSeen": "Последний раз",
|
||||
"userDashboard.version": "Версия",
|
||||
"userDashboard.connectNew": "Подключить новое устройство",
|
||||
"userDashboard.connectNewHint": "Установите Верстак на новом устройстве и выполните регистрацию.",
|
||||
"userDashboard.revokeConfirm": "Отозвать устройство?",
|
||||
"userDashboard.revokePrompt": "Введите пароль для подтверждения:",
|
||||
"userDashboard.noDevices": "Нет устройств",
|
||||
"userDashboard.active": "Активно",
|
||||
"userDashboard.revoked": "Отозвано",
|
||||
"userDashboard.revoke": "Отозвать",
|
||||
"admin.login": "Вход администратора",
|
||||
"admin.username": "Имя пользователя",
|
||||
"admin.password": "Пароль",
|
||||
"admin.loginBtn": "Войти",
|
||||
"admin.dashboard": "Панель управления",
|
||||
"admin.deviceCount": "Устройств",
|
||||
"admin.opsCount": "Операций",
|
||||
"admin.devices": "Устройства",
|
||||
"admin.noDevices": "Нет устройств",
|
||||
"admin.device": "Устройство",
|
||||
"admin.user": "Пользователь",
|
||||
"admin.version": "Версия",
|
||||
"admin.status": "Статус",
|
||||
"admin.lastSeen": "Последний раз",
|
||||
"admin.active": "Активно",
|
||||
"admin.revoked": "Отозвано",
|
||||
"admin.revoke": "Отозвать",
|
||||
"admin.smtp": "SMTP",
|
||||
"admin.users": "Пользователи",
|
||||
"admin.usersHeading": "Пользователи",
|
||||
"admin.healthCheck": "Проверка здоровья",
|
||||
"admin.smtpServer": "SMTP сервер",
|
||||
"admin.smtpPort": "SMTP порт",
|
||||
"admin.smtpType": "Тип шифрования",
|
||||
"admin.smtpNoEncryption": "Без шифрования",
|
||||
"admin.smtpUsername": "Имя пользователя SMTP",
|
||||
"admin.smtpPassword": "Пароль SMTP",
|
||||
"admin.smtpFrom": "Отправитель",
|
||||
"admin.smtpServerURL": "URL сервера",
|
||||
"admin.smtpSave": "Сохранить",
|
||||
"admin.smtpTest": "Тест",
|
||||
"admin.smtpTitle": "Настройки SMTP",
|
||||
"admin.smtpTesting": "Проверка...",
|
||||
"admin.smtpPassed": "✓ Тест пройден",
|
||||
"admin.revokeConfirm": "Вы уверены?",
|
||||
"common.loading": "Загрузка...",
|
||||
"common.ok": "OK",
|
||||
"common.error": "Ошибка",
|
||||
"admin.filterPlaceholder": "Поиск...",
|
||||
"admin.email": "Email",
|
||||
"admin.actions": "Действия",
|
||||
"admin.confirmTitle": "Подтверждение",
|
||||
"admin.modalCancel": "Отмена",
|
||||
"admin.modalConfirm": "Подтвердить",
|
||||
"admin.editUser": "Редактировать пользователя",
|
||||
"admin.editBtn": "Сохранить",
|
||||
"admin.resultTitle": "Результат",
|
||||
"admin.confirmed": "Подтверждён",
|
||||
"admin.unconfirmed": "Не подтверждён",
|
||||
"admin.blocked": "Заблокирован",
|
||||
"admin.unblock": "Разблокировать",
|
||||
"admin.block": "Заблокировать",
|
||||
"admin.resetPassword": "Сбросить пароль",
|
||||
"admin.noUsers": "Нет пользователей",
|
||||
"admin.resetPasswordConfirm": "Сбросить пароль",
|
||||
"admin.resetPasswordMessage": "Новый пароль: ",
|
||||
"admin.resetBtn": "Сбросить",
|
||||
"admin.deleteUser": "Удалить",
|
||||
"admin.deleteUserMessage": "Удалить пользователя %s?",
|
||||
"admin.deleteBtn": "Удалить",
|
||||
"admin.unblockUserTitle": "Разблокировать",
|
||||
"admin.blockUserTitle": "Заблокировать",
|
||||
"admin.unblockUserMessage": "Разблокировать пользователя?",
|
||||
"admin.blockUserMessage": "Заблокировать пользователя?",
|
||||
"admin.createUser": "Создать пользователя",
|
||||
"admin.createUserBtn": "Создать",
|
||||
},
|
||||
"en": {
|
||||
"server.registerTitle": "Registration",
|
||||
"server.register": "Register",
|
||||
"server.username": "Username",
|
||||
"server.email": "Email",
|
||||
"server.password": "Password",
|
||||
"server.registerBtn": "Register",
|
||||
"server.alreadyHaveAccount": "Already have an account?",
|
||||
"server.loginBtn": "Login",
|
||||
"server.loginTitle": "Login",
|
||||
"server.usernameOrEmail": "Username or email",
|
||||
"server.forgotPassword": "Forgot password?",
|
||||
"server.adminLink": "Admin",
|
||||
"server.dashboard": "Dashboard",
|
||||
"server.allFieldsRequired": "All fields are required",
|
||||
"server.back": "Back",
|
||||
"server.emailConfirmBody": "Confirm registration: %s",
|
||||
"server.emailConfirmSubject": "Verstak — email confirmation",
|
||||
"server.registrationSuccess": "Registration successful",
|
||||
"server.registrationEmailSent": "Confirmation link has been sent.",
|
||||
"server.registrationCheckEmail": "Check your email and confirm your account.",
|
||||
"server.registrationAutoMessage": "Email is not confirmed. Confirmation token logged to server.",
|
||||
"server.resetPasswordTitle": "Reset Password",
|
||||
"server.resetPassword": "Reset Password",
|
||||
"server.resetInstruction": "Enter your email to receive a reset link.",
|
||||
"server.sendLink": "Send link",
|
||||
"server.backToLogin": "Back to login",
|
||||
"server.emailSentTitle": "Email sent",
|
||||
"server.emailSent": "Email sent",
|
||||
"server.emailSentMessage": "If the account exists, a reset link has been sent.",
|
||||
"server.goHome": "Go home",
|
||||
"server.newPasswordTitle": "New Password",
|
||||
"server.newPassword": "New Password",
|
||||
"server.passwordConfirm": "Confirm Password",
|
||||
"server.save": "Save",
|
||||
"server.passwordChanged": "Password changed",
|
||||
"server.passwordChangedMessage": "Password has been changed. You can now login.",
|
||||
"server.emailConfirmed": "Email confirmed",
|
||||
"server.emailConfirmedMessage": "Account activated. You can now login.",
|
||||
"server.needEmail": "Enter email",
|
||||
"server.passwordsDoNotMatch": "Passwords do not match",
|
||||
"server.newPasswordResult": "New password for %s:\n",
|
||||
"server.logout": "Logout",
|
||||
"server.error": "Error",
|
||||
"server.internalError": "Something went wrong. Please try again.",
|
||||
"server.registrationFailed": "Could not register the account. Please try again.",
|
||||
"userDashboard.devices": "Devices",
|
||||
"userDashboard.device": "Device",
|
||||
"userDashboard.status": "Status",
|
||||
"userDashboard.connected": "Connected",
|
||||
"userDashboard.lastSeen": "Last seen",
|
||||
"userDashboard.version": "Version",
|
||||
"userDashboard.connectNew": "Connect new device",
|
||||
"userDashboard.connectNewHint": "Install Verstak on a new device and register it.",
|
||||
"userDashboard.revokeConfirm": "Revoke device?",
|
||||
"userDashboard.revokePrompt": "Enter password to confirm:",
|
||||
"userDashboard.noDevices": "No devices",
|
||||
"userDashboard.active": "Active",
|
||||
"userDashboard.revoked": "Revoked",
|
||||
"userDashboard.revoke": "Revoke",
|
||||
"admin.login": "Admin Login",
|
||||
"admin.username": "Username",
|
||||
"admin.password": "Password",
|
||||
"admin.loginBtn": "Login",
|
||||
"admin.dashboard": "Admin Dashboard",
|
||||
"admin.deviceCount": "Devices",
|
||||
"admin.opsCount": "Operations",
|
||||
"admin.devices": "Devices",
|
||||
"admin.noDevices": "No devices",
|
||||
"admin.device": "Device",
|
||||
"admin.user": "User",
|
||||
"admin.version": "Version",
|
||||
"admin.status": "Status",
|
||||
"admin.lastSeen": "Last seen",
|
||||
"admin.active": "Active",
|
||||
"admin.revoked": "Revoked",
|
||||
"admin.revoke": "Revoke",
|
||||
"admin.smtp": "SMTP",
|
||||
"admin.users": "Users",
|
||||
"admin.usersHeading": "Users",
|
||||
"admin.healthCheck": "Health Check",
|
||||
"admin.smtpServer": "SMTP Server",
|
||||
"admin.smtpPort": "SMTP Port",
|
||||
"admin.smtpType": "Encryption",
|
||||
"admin.smtpNoEncryption": "None",
|
||||
"admin.smtpUsername": "SMTP Username",
|
||||
"admin.smtpPassword": "SMTP Password",
|
||||
"admin.smtpFrom": "From",
|
||||
"admin.smtpServerURL": "Server URL",
|
||||
"admin.smtpSave": "Save",
|
||||
"admin.smtpTest": "Test",
|
||||
"admin.smtpTitle": "SMTP Settings",
|
||||
"admin.smtpTesting": "Testing...",
|
||||
"admin.smtpPassed": "✓ Test passed",
|
||||
"admin.smtpRequired": "Enter the SMTP server, port, and sender.",
|
||||
"admin.smtpTestFailed": "Could not test the SMTP settings. Please try again.",
|
||||
"admin.revokeConfirm": "Are you sure?",
|
||||
"common.loading": "Loading...",
|
||||
"common.ok": "OK",
|
||||
"common.error": "Error",
|
||||
"admin.filterPlaceholder": "Search...",
|
||||
"admin.email": "Email",
|
||||
"admin.actions": "Actions",
|
||||
"admin.confirmTitle": "Confirm",
|
||||
"admin.modalCancel": "Cancel",
|
||||
"admin.modalConfirm": "Confirm",
|
||||
"admin.editUser": "Edit User",
|
||||
"admin.editBtn": "Save",
|
||||
"admin.resultTitle": "Result",
|
||||
"admin.confirmed": "Confirmed",
|
||||
"admin.unconfirmed": "Unconfirmed",
|
||||
"admin.blocked": "Blocked",
|
||||
"admin.unblock": "Unblock",
|
||||
"admin.block": "Block",
|
||||
"admin.resetPassword": "Reset Password",
|
||||
"admin.noUsers": "No users",
|
||||
"admin.resetPasswordConfirm": "Reset Password",
|
||||
"admin.resetPasswordMessage": "New password: ",
|
||||
"admin.resetBtn": "Reset",
|
||||
"admin.deleteUser": "Delete",
|
||||
"admin.deleteUserMessage": "Delete user %s?",
|
||||
"admin.deleteBtn": "Delete",
|
||||
"admin.unblockUserTitle": "Unblock",
|
||||
"admin.blockUserTitle": "Block",
|
||||
"admin.unblockUserMessage": "Unblock user?",
|
||||
"admin.blockUserMessage": "Block user?",
|
||||
"admin.createUser": "Create User",
|
||||
"admin.createUserBtn": "Create",
|
||||
"admin.createUserFailed": "Could not create the user. Please try again.",
|
||||
"server.registerTitle": "Registration",
|
||||
"server.register": "Register",
|
||||
"server.username": "Username",
|
||||
"server.email": "Email",
|
||||
"server.password": "Password",
|
||||
"server.registerBtn": "Register",
|
||||
"server.alreadyHaveAccount": "Already have an account?",
|
||||
"server.loginBtn": "Login",
|
||||
"server.loginTitle": "Login",
|
||||
"server.usernameOrEmail": "Username or email",
|
||||
"server.forgotPassword": "Forgot password?",
|
||||
"server.adminLink": "Admin",
|
||||
"server.dashboard": "Dashboard",
|
||||
"server.allFieldsRequired": "All fields are required",
|
||||
"server.back": "Back",
|
||||
"server.emailConfirmBody": "Confirm registration: %s",
|
||||
"server.emailConfirmSubject": "Verstak — email confirmation",
|
||||
"server.registrationSuccess": "Registration successful",
|
||||
"server.registrationEmailSent": "Confirmation link has been sent.",
|
||||
"server.registrationCheckEmail":"Check your email and confirm your account.",
|
||||
"server.registrationAutoMessage":"Email is not confirmed. Confirmation token logged to server.",
|
||||
"server.resetPasswordTitle": "Reset Password",
|
||||
"server.resetPassword": "Reset Password",
|
||||
"server.resetInstruction": "Enter your email to receive a reset link.",
|
||||
"server.sendLink": "Send link",
|
||||
"server.backToLogin": "Back to login",
|
||||
"server.emailSentTitle": "Email sent",
|
||||
"server.emailSent": "Email sent",
|
||||
"server.emailSentMessage": "If the account exists, a reset link has been sent.",
|
||||
"server.goHome": "Go home",
|
||||
"server.newPasswordTitle": "New Password",
|
||||
"server.newPassword": "New Password",
|
||||
"server.passwordConfirm": "Confirm Password",
|
||||
"server.save": "Save",
|
||||
"server.passwordChanged": "Password changed",
|
||||
"server.passwordChangedMessage":"Password has been changed. You can now login.",
|
||||
"server.emailConfirmed": "Email confirmed",
|
||||
"server.emailConfirmedMessage": "Account activated. You can now login.",
|
||||
"server.needEmail": "Enter email",
|
||||
"server.passwordsDoNotMatch": "Passwords do not match",
|
||||
"server.newPasswordResult": "New password for %s:\n",
|
||||
"server.logout": "Logout",
|
||||
"server.error": "Error",
|
||||
"userDashboard.devices": "Devices",
|
||||
"userDashboard.device": "Device",
|
||||
"userDashboard.status": "Status",
|
||||
"userDashboard.connected": "Connected",
|
||||
"userDashboard.lastSeen": "Last seen",
|
||||
"userDashboard.version": "Version",
|
||||
"userDashboard.connectNew": "Connect new device",
|
||||
"userDashboard.connectNewHint": "Install Verstak on a new device and register it.",
|
||||
"userDashboard.revokeConfirm": "Revoke device?",
|
||||
"userDashboard.revokePrompt": "Enter password to confirm:",
|
||||
"userDashboard.noDevices": "No devices",
|
||||
"userDashboard.active": "Active",
|
||||
"userDashboard.revoked": "Revoked",
|
||||
"userDashboard.revoke": "Revoke",
|
||||
"admin.login": "Admin Login",
|
||||
"admin.username": "Username",
|
||||
"admin.password": "Password",
|
||||
"admin.loginBtn": "Login",
|
||||
"admin.dashboard": "Admin Dashboard",
|
||||
"admin.deviceCount": "Devices",
|
||||
"admin.opsCount": "Operations",
|
||||
"admin.devices": "Devices",
|
||||
"admin.noDevices": "No devices",
|
||||
"admin.device": "Device",
|
||||
"admin.user": "User",
|
||||
"admin.version": "Version",
|
||||
"admin.status": "Status",
|
||||
"admin.lastSeen": "Last seen",
|
||||
"admin.active": "Active",
|
||||
"admin.revoked": "Revoked",
|
||||
"admin.revoke": "Revoke",
|
||||
"admin.smtp": "SMTP",
|
||||
"admin.users": "Users",
|
||||
"admin.usersHeading": "Users",
|
||||
"admin.healthCheck": "Health Check",
|
||||
"admin.smtpServer": "SMTP Server",
|
||||
"admin.smtpPort": "SMTP Port",
|
||||
"admin.smtpType": "Encryption",
|
||||
"admin.smtpNoEncryption": "None",
|
||||
"admin.smtpUsername": "SMTP Username",
|
||||
"admin.smtpPassword": "SMTP Password",
|
||||
"admin.smtpFrom": "From",
|
||||
"admin.smtpServerURL": "Server URL",
|
||||
"admin.smtpSave": "Save",
|
||||
"admin.smtpTest": "Test",
|
||||
"admin.smtpTitle": "SMTP Settings",
|
||||
"admin.smtpTesting": "Testing...",
|
||||
"admin.smtpPassed": "✓ Test passed",
|
||||
"admin.revokeConfirm": "Are you sure?",
|
||||
"common.loading": "Loading...",
|
||||
"common.ok": "OK",
|
||||
"common.error": "Error",
|
||||
"admin.filterPlaceholder": "Search...",
|
||||
"admin.email": "Email",
|
||||
"admin.actions": "Actions",
|
||||
"admin.confirmTitle": "Confirm",
|
||||
"admin.modalCancel": "Cancel",
|
||||
"admin.modalConfirm": "Confirm",
|
||||
"admin.editUser": "Edit User",
|
||||
"admin.editBtn": "Save",
|
||||
"admin.resultTitle": "Result",
|
||||
"admin.confirmed": "Confirmed",
|
||||
"admin.unconfirmed": "Unconfirmed",
|
||||
"admin.blocked": "Blocked",
|
||||
"admin.unblock": "Unblock",
|
||||
"admin.block": "Block",
|
||||
"admin.resetPassword": "Reset Password",
|
||||
"admin.noUsers": "No users",
|
||||
"admin.resetPasswordConfirm": "Reset Password",
|
||||
"admin.resetPasswordMessage": "New password: ",
|
||||
"admin.resetBtn": "Reset",
|
||||
"admin.deleteUser": "Delete",
|
||||
"admin.deleteUserMessage": "Delete user %s?",
|
||||
"admin.deleteBtn": "Delete",
|
||||
"admin.unblockUserTitle": "Unblock",
|
||||
"admin.blockUserTitle": "Block",
|
||||
"admin.unblockUserMessage": "Unblock user?",
|
||||
"admin.blockUserMessage": "Block user?",
|
||||
"admin.createUser": "Create User",
|
||||
"admin.createUserBtn": "Create",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,41 +64,6 @@ func TestConfigSetAdmin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminDashboardSMTPSecuritySelectUsesApplicationStyles(t *testing.T) {
|
||||
html := adminDashboardHTML("en", 0, 0, "", "", "", "", "starttls", "")
|
||||
|
||||
for _, expected := range []string{
|
||||
`<select name="smtp_security" class="form-select">`,
|
||||
`.form-select{`,
|
||||
`appearance:none`,
|
||||
`background-image:linear-gradient`,
|
||||
`.form-select option{background:#13131f;color:#e4e4ef}`,
|
||||
`.form-select:focus{outline:none;border-color:#6366f1`,
|
||||
} {
|
||||
if !strings.Contains(html, expected) {
|
||||
t.Errorf("SMTP security select is missing application styling %q", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserFacingServerErrorsDoNotExposeInternalDetails(t *testing.T) {
|
||||
for _, name := range []string{"handlers_auth.go", "handlers_api.go", "handlers_admin.go", "handlers_web_user.go"} {
|
||||
source, err := os.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", name, err)
|
||||
}
|
||||
for _, forbidden := range []string{
|
||||
"jsonErr(w, 500, err.Error())",
|
||||
"http.Error(w, err.Error(), 500)",
|
||||
"errorPageHTML(locale, t(locale, \"common.error\"), err.Error()",
|
||||
} {
|
||||
if strings.Contains(string(source), forbidden) {
|
||||
t.Errorf("%s exposes an internal error with %q", name, forbidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncPushPullStoresSequencedOps(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s, err := NewServer(filepath.Join(dir, "test.db"), filepath.Join(dir, "data"), &Config{Port: 47732})
|
||||
|
|
|
|||
|
|
@ -312,9 +312,6 @@ input:focus{outline:none;border-color:#6366f1}
|
|||
.form-row{display:flex;gap:8px;margin-bottom:8px;align-items:center}
|
||||
.form-row label{font-size:12px;color:#888;min-width:80px;flex-shrink:0}
|
||||
.form-row input{flex:1}
|
||||
.form-select{font-family:inherit;font-size:14px;padding:8px 32px 8px 12px;border:1px solid #2a2a3c;background-color:#13131f;color:#e4e4ef;border-radius:6px;flex:1;box-sizing:border-box;appearance:none;background-image:linear-gradient(45deg,transparent 50%%,#8b93aa 50%%),linear-gradient(135deg,#8b93aa 50%%,transparent 50%%);background-position:calc(100%% - 16px) 50%%,calc(100%% - 11px) 50%%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}
|
||||
.form-select option{background:#13131f;color:#e4e4ef}
|
||||
.form-select:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 2px rgba(99,102,241,.24)}
|
||||
.toolbar{display:flex;gap:8px;margin:16px 0;flex-wrap:wrap}
|
||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:100}
|
||||
.modal{background:#1a1a28;border:1px solid #2a2a3c;border-radius:12px;padding:24px;width:420px;max-width:90vw;position:relative;max-height:80vh;overflow-y:auto}
|
||||
|
|
@ -383,7 +380,7 @@ function testSMTP(){
|
|||
<form action="/admin/api/smtp" method="POST">
|
||||
<div class="form-row"><label>%[18]s</label><input name="smtp_host" value="%[32]s" placeholder="smtp.example.com"></div>
|
||||
<div class="form-row"><label>%[19]s</label><input name="smtp_port" value="%[33]s" placeholder="587"></div>
|
||||
<div class="form-row"><label>%[20]s</label><select name="smtp_security" class="form-select">
|
||||
<div class="form-row"><label>%[20]s</label><select name="smtp_security" style="font-family:inherit;font-size:14px;padding:8px 12px;border:1px solid #2a2a3c;background:#13131f;color:#e4e4ef;border-radius:6px;flex:1;box-sizing:border-box">
|
||||
<option value="starttls"%[34]s>STARTTLS</option>
|
||||
<option value="tls"%[35]s>TLS</option>
|
||||
<option value="none"%[36]s>%[21]s</option>
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
## Highlights
|
||||
|
||||
- Client-facing errors no longer expose internal server details.
|
||||
- The SMTP security selector follows the rest of the server settings UI.
|
||||
- The release workflow uploads only artifacts belonging to the requested version and marks alpha versions as prereleases.
|
||||
|
|
@ -6,7 +6,6 @@ VERSION="${1:-}"
|
|||
REPOSITORY="mirivlad/verstak-sync-server"
|
||||
RELEASE_SCRIPT="${VERSTAK_RELEASE_SCRIPT:-$ROOT/scripts/release.sh}"
|
||||
RELEASE_DIR="${VERSTAK_RELEASE_DIR:-$ROOT/release}"
|
||||
RELEASE_NOTES_DIR="${VERSTAK_RELEASE_NOTES_DIR:-$ROOT/release-notes}"
|
||||
GIT_BIN="${GIT_BIN:-git}"
|
||||
GH_BIN="${GH_BIN:-gh}"
|
||||
|
||||
|
|
@ -40,15 +39,15 @@ fi
|
|||
|
||||
"$RELEASE_SCRIPT" "$VERSION"
|
||||
|
||||
ARCHIVE="$RELEASE_DIR/verstak-sync-server-linux-amd64-$VERSION.tar.gz"
|
||||
if [[ ! -f "$ARCHIVE" ]]; then
|
||||
echo "release archive not found: $ARCHIVE" >&2
|
||||
exit 1
|
||||
fi
|
||||
ASSETS=("$ARCHIVE")
|
||||
shopt -s nullglob
|
||||
ASSETS=("$RELEASE_DIR"/*.tar.gz "$RELEASE_DIR"/*.tgz)
|
||||
if [[ -f "$RELEASE_DIR/SHA256SUMS" ]]; then
|
||||
ASSETS+=("$RELEASE_DIR/SHA256SUMS")
|
||||
fi
|
||||
if [[ ${#ASSETS[@]} -eq 0 ]]; then
|
||||
echo "no release assets found in $RELEASE_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if "$GIT_BIN" rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
|
||||
if [[ "$("$GIT_BIN" rev-parse "${VERSION}^{commit}")" != "$HEAD" ]]; then
|
||||
|
|
@ -63,26 +62,12 @@ fi
|
|||
if "$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" >/dev/null 2>&1; then
|
||||
"$GH_BIN" release upload "$VERSION" "${ASSETS[@]}" --repo "$REPOSITORY" --clobber
|
||||
else
|
||||
NOTES_FILE="$RELEASE_NOTES_DIR/$VERSION.md"
|
||||
if [[ ! -s "$NOTES_FILE" ]]; then
|
||||
echo "human-readable release notes are required: $NOTES_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_OPTIONS=(--notes-file "$NOTES_FILE" --generate-notes --verify-tag)
|
||||
PREVIOUS_TAG="$("$GIT_BIN" describe --tags --abbrev=0 "${HEAD}^" 2>/dev/null || true)"
|
||||
if [[ -n "$PREVIOUS_TAG" ]]; then
|
||||
RELEASE_OPTIONS+=(--notes-start-tag "$PREVIOUS_TAG")
|
||||
fi
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
RELEASE_OPTIONS+=(--prerelease)
|
||||
else
|
||||
RELEASE_OPTIONS+=(--latest)
|
||||
fi
|
||||
"$GH_BIN" release create "$VERSION" "${ASSETS[@]}" \
|
||||
--repo "$REPOSITORY" \
|
||||
--title "Verstak Sync Server $VERSION" \
|
||||
"${RELEASE_OPTIONS[@]}"
|
||||
--generate-notes \
|
||||
--latest \
|
||||
--verify-tag
|
||||
fi
|
||||
|
||||
echo "GitHub release:"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ PUBLISHER="$ROOT/scripts/publish-github-release.sh"
|
|||
VERSION="v0.0.0-test"
|
||||
REPOSITORY="mirivlad/verstak-sync-server"
|
||||
ASSET_NAME="verstak-sync-server-linux-amd64-${VERSION}.tar.gz"
|
||||
STALE_ASSET="verstak-sync-server-linux-amd64-v0.0.0-stale.tar.gz"
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
|
|
@ -15,10 +14,9 @@ if [[ ! -x "$PUBLISHER" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$WORK/bin" "$WORK/release" "$WORK/release-notes" "$WORK/state"
|
||||
mkdir -p "$WORK/bin" "$WORK/release" "$WORK/state"
|
||||
LOG="$WORK/log"
|
||||
export LOG
|
||||
printf '## Highlights\n\nHuman-readable release notes.\n' > "$WORK/release-notes/$VERSION.md"
|
||||
|
||||
cat > "$WORK/release.sh" <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
|
|
@ -29,8 +27,6 @@ printf 'checksum\n' > "$VERSTAK_RELEASE_DIR/SHA256SUMS"
|
|||
SCRIPT
|
||||
chmod +x "$WORK/release.sh"
|
||||
|
||||
printf 'stale archive\n' > "$WORK/release/$STALE_ASSET"
|
||||
|
||||
cat > "$WORK/bin/git" <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
|
@ -49,7 +45,6 @@ case "${1:-}" in
|
|||
fi
|
||||
echo test-commit
|
||||
;;
|
||||
describe) echo v0.0.0-previous ;;
|
||||
tag) touch "$TEST_STATE/tag"; printf 'tag:%s\n' "${3:-}" >> "$LOG" ;;
|
||||
push) printf 'push:%s:%s\n' "${2:-}" "${3:-}" >> "$LOG" ;;
|
||||
*) echo "unexpected git invocation: $*" >&2; exit 1 ;;
|
||||
|
|
@ -75,7 +70,6 @@ chmod +x "$WORK/bin/gh"
|
|||
run_publisher() {
|
||||
VERSTAK_RELEASE_SCRIPT="$WORK/release.sh" \
|
||||
VERSTAK_RELEASE_DIR="$WORK/release" \
|
||||
VERSTAK_RELEASE_NOTES_DIR="$WORK/release-notes" \
|
||||
GIT_BIN="$WORK/bin/git" \
|
||||
GH_BIN="$WORK/bin/gh" \
|
||||
EXPECTED_ROOT="$ROOT" \
|
||||
|
|
@ -90,18 +84,6 @@ grep -Fqx "push:origin:refs/tags/$VERSION" "$LOG"
|
|||
grep -F "release create $VERSION" "$LOG" >/dev/null
|
||||
grep -F "$ASSET_NAME" "$LOG" >/dev/null
|
||||
grep -F "SHA256SUMS" "$LOG" >/dev/null
|
||||
grep -F -- "--notes-file $WORK/release-notes/$VERSION.md" "$LOG" >/dev/null
|
||||
grep -F -- "--generate-notes" "$LOG" >/dev/null
|
||||
grep -F -- "--notes-start-tag v0.0.0-previous" "$LOG" >/dev/null
|
||||
grep -F -- "--prerelease" "$LOG" >/dev/null
|
||||
if grep -F -- "--latest" "$LOG" >/dev/null; then
|
||||
echo "alpha release was incorrectly marked latest" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -F "$STALE_ASSET" "$LOG" >/dev/null; then
|
||||
echo "publisher uploaded an archive from a different release version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_publisher
|
||||
grep -F "release upload $VERSION" "$LOG" >/dev/null
|
||||
|
|
|
|||
Loading…
Reference in New Issue