+notification from site +version bump fix
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful

This commit is contained in:
Stephan D
2025-11-17 22:20:17 +01:00
parent c6a56071b5
commit 9dbf77a9a8
21 changed files with 543 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ matrix:
NOTIFICATION_MONGO_SECRET_PATH: sendico/db
NOTIFICATION_MAIL_SECRET_PATH: sendico/notification/mail
NOTIFICATION_API_SECRET_PATH: sendico/api/endpoint
NOTIFICATION_TELEGRAM_SECRET_PATH: sendico/notification/telegram
NOTIFICATION_ENV: prod
when:

View File

@@ -53,14 +53,21 @@ api:
password_env: MAIL_SECRET
host: "smtp.mail.ru"
port: 465
from: "MeetX Tech"
from: "Sendico Tech"
network_timeout: 10
telegram:
bot_token_env: TELEGRAM_BOT_TOKEN
chat_id_env: TELEGRAM_CHAT_ID
thread_id_env: TELEGRAM_THREAD_ID
api_url: "https://api.telegram.org"
timeout_seconds: 10
parse_mode: ""
localizer:
path: "./i18n"
languages: ["en", "ru", "uk"]
service_name: "Sendico"
support: "support@meetx.space"
support: "support@sendico.io"
app:
@@ -82,4 +89,4 @@ database:
collection_name_env: PERMISSION_COLLECTION
database_name_env: MONGO_DATABASE
timeout_seconds_env: PERMISSION_TIMEOUT
is_filtered_env: PERMISSION_IS_FILTERED
is_filtered_env: PERMISSION_IS_FILTERED

View File

@@ -1,6 +1,16 @@
package notificationimp
type Config struct {
Driver string `yaml:"driver"`
Settings map[string]any `yaml:"settings,omitempty"`
Driver string `yaml:"driver"`
Settings map[string]any `yaml:"settings,omitempty"`
Telegram *TelegramConfig `yaml:"telegram"`
}
type TelegramConfig struct {
BotTokenEnv string `yaml:"bot_token_env"`
ChatIDEnv string `yaml:"chat_id_env"`
ThreadIDEnv string `yaml:"thread_id_env,omitempty"`
APIURL string `yaml:"api_url,omitempty"`
ParseMode string `yaml:"parse_mode,omitempty"`
TimeoutSeconds int `yaml:"timeout_seconds"`
}

View File

@@ -2,13 +2,17 @@ package notificationimp
import (
"context"
"fmt"
"github.com/tech/sendico/notification/interface/api"
mmail "github.com/tech/sendico/notification/internal/server/notificationimp/mail"
"github.com/tech/sendico/notification/internal/server/notificationimp/telegram"
"github.com/tech/sendico/pkg/domainprovider"
na "github.com/tech/sendico/pkg/messaging/notifications/account"
ni "github.com/tech/sendico/pkg/messaging/notifications/invitation"
snotifications "github.com/tech/sendico/pkg/messaging/notifications/site"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice"
"go.uber.org/zap"
)
@@ -17,6 +21,7 @@ type NotificationAPI struct {
logger mlogger.Logger
client mmail.Client
dp domainprovider.DomainProvider
tg telegram.Client
}
func (a *NotificationAPI) Name() mservice.Type {
@@ -33,11 +38,22 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
}
p.logger = a.Logger().Named(p.Name())
if a.Config().Notification == nil {
return nil, fmt.Errorf("notification configuration is missing")
}
if a.Config().Notification.Telegram == nil {
return nil, fmt.Errorf("telegram configuration is missing")
}
var err error
if p.client, err = mmail.CreateMailClient(p.logger.Named("mailer"), p.Name(), a.Register().Producer(), a.Localizer(), a.DomainProvider(), a.Config().Notification); err != nil {
p.logger.Error("Failed to create mail connection", zap.Error(err), zap.String("driver", a.Config().Notification.Driver))
return nil, err
}
if p.tg, err = telegram.NewClient(p.logger.Named("telegram"), a.Config().Notification.Telegram); err != nil {
p.logger.Error("Failed to create telegram client", zap.Error(err))
return nil, err
}
db, err := a.DBFactory().NewAccountDB()
if err != nil {
@@ -64,5 +80,22 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
return nil, err
}
if err := a.Register().Consumer(snotifications.NewDemoRequestProcessor(p.logger, p.onDemoRequest)); err != nil {
p.logger.Error("Failed to register demo request handler", zap.Error(err))
return nil, err
}
return p, nil
}
func (a *NotificationAPI) onDemoRequest(ctx context.Context, request *model.DemoRequest) error {
if a.tg == nil {
return fmt.Errorf("telegram client is not configured")
}
if err := a.tg.SendDemoRequest(ctx, request); err != nil {
a.logger.Warn("Failed to send demo request via telegram", zap.Error(err))
return err
}
a.logger.Info("Demo request sent via Telegram", zap.String("name", request.Name), zap.String("organization", request.OrganizationName))
return nil
}

View File

@@ -0,0 +1,149 @@
package telegram
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
notconfig "github.com/tech/sendico/notification/interface/services/notification/config"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
const defaultAPIURL = "https://api.telegram.org"
type Client interface {
SendDemoRequest(ctx context.Context, request *model.DemoRequest) error
}
type client struct {
logger mlogger.Logger
httpClient *http.Client
apiURL string
botToken string
chatID string
threadID *int64
parseMode string
}
type sendMessagePayload struct {
ChatID string `json:"chat_id"`
Text string `json:"text"`
ParseMode string `json:"parse_mode,omitempty"`
ThreadID *int64 `json:"message_thread_id,omitempty"`
DisablePreview bool `json:"disable_web_page_preview,omitempty"`
DisableNotify bool `json:"disable_notification,omitempty"`
ProtectContent bool `json:"protect_content,omitempty"`
}
func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, error) {
if cfg == nil {
return nil, fmt.Errorf("telegram configuration is not provided")
}
token := strings.TrimSpace(os.Getenv(cfg.BotTokenEnv))
if token == "" {
return nil, fmt.Errorf("telegram bot token env %s is empty", cfg.BotTokenEnv)
}
chatID := strings.TrimSpace(os.Getenv(cfg.ChatIDEnv))
if chatID == "" {
return nil, fmt.Errorf("telegram chat id env %s is empty", cfg.ChatIDEnv)
}
var threadID *int64
if env := strings.TrimSpace(cfg.ThreadIDEnv); env != "" {
raw := strings.TrimSpace(os.Getenv(env))
if raw != "" {
val, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return nil, fmt.Errorf("telegram thread id env %s is invalid: %w", env, err)
}
threadID = &val
}
}
timeout := time.Duration(cfg.TimeoutSeconds) * time.Second
if timeout <= 0 {
timeout = 10 * time.Second
}
apiURL := strings.TrimSpace(cfg.APIURL)
if apiURL == "" {
apiURL = defaultAPIURL
}
return &client{
logger: logger.Named("telegram"),
httpClient: &http.Client{
Timeout: timeout,
},
apiURL: strings.TrimRight(apiURL, "/"),
botToken: token,
chatID: chatID,
threadID: threadID,
parseMode: strings.TrimSpace(cfg.ParseMode),
}, nil
}
func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest) error {
if request == nil {
return fmt.Errorf("demo request payload is nil")
}
message := buildMessage(request)
payload := sendMessagePayload{
ChatID: c.chatID,
Text: message,
ParseMode: c.parseMode,
ThreadID: c.threadID,
DisablePreview: true,
}
return c.sendMessage(ctx, payload)
}
func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) error {
body, err := json.Marshal(&payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint(), bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
return nil
}
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
return fmt.Errorf("telegram sendMessage failed with status %d: %s", resp.StatusCode, string(respBody))
}
func (c *client) endpoint() string {
return fmt.Sprintf("%s/bot%s/sendMessage", c.apiURL, c.botToken)
}
func buildMessage(req *model.DemoRequest) string {
var builder strings.Builder
builder.WriteString("New demo request received\n")
builder.WriteString(fmt.Sprintf("Name: %s\n", req.Name))
builder.WriteString(fmt.Sprintf("Organization: %s\n", req.OrganizationName))
builder.WriteString(fmt.Sprintf("Phone: %s\n", req.Phone))
builder.WriteString(fmt.Sprintf("Work email: %s\n", req.WorkEmail))
builder.WriteString(fmt.Sprintf("Payout volume: %s\n", req.PayoutVolume))
if req.Comment != "" {
builder.WriteString(fmt.Sprintf("Comment: %s\n", req.Comment))
}
return builder.String()
}

View File

@@ -0,0 +1,47 @@
package notifications
import (
"fmt"
gmessaging "github.com/tech/sendico/pkg/generated/gmessaging"
messaging "github.com/tech/sendico/pkg/messaging/envelope"
"github.com/tech/sendico/pkg/model"
nm "github.com/tech/sendico/pkg/model/notification"
"github.com/tech/sendico/pkg/mservice"
"google.golang.org/protobuf/proto"
)
type DemoRequestNotification struct {
messaging.Envelope
request *model.DemoRequest
}
func (drn *DemoRequestNotification) Serialize() ([]byte, error) {
if drn.request == nil {
return nil, fmt.Errorf("demo request payload is empty")
}
msg := gmessaging.DemoRequestEvent{
Name: drn.request.Name,
OrganizationName: drn.request.OrganizationName,
Phone: drn.request.Phone,
WorkEmail: drn.request.WorkEmail,
PayoutVolume: drn.request.PayoutVolume,
Comment: drn.request.Comment,
}
data, err := proto.Marshal(&msg)
if err != nil {
return nil, err
}
return drn.Envelope.Wrap(data)
}
func NewDemoRequestEvent() model.NotificationEvent {
return model.NewNotification(mservice.Site, nm.NACreated)
}
func NewDemoRequestEnvelope(sender string, request *model.DemoRequest) messaging.Envelope {
return &DemoRequestNotification{
Envelope: messaging.CreateEnvelope(sender, NewDemoRequestEvent()),
request: request,
}
}

View File

@@ -0,0 +1,11 @@
package notifications
import (
messaging "github.com/tech/sendico/pkg/messaging/envelope"
internalsite "github.com/tech/sendico/pkg/messaging/internal/notifications/site"
"github.com/tech/sendico/pkg/model"
)
func DemoRequest(sender string, request *model.DemoRequest) messaging.Envelope {
return internalsite.NewDemoRequestEnvelope(sender, request)
}

View File

@@ -0,0 +1,9 @@
package notifications
import (
"context"
"github.com/tech/sendico/pkg/model"
)
type DemoRequestHandler = func(context.Context, *model.DemoRequest) error

View File

@@ -0,0 +1,50 @@
package notifications
import (
"context"
gmessaging "github.com/tech/sendico/pkg/generated/gmessaging"
me "github.com/tech/sendico/pkg/messaging/envelope"
internalsite "github.com/tech/sendico/pkg/messaging/internal/notifications/site"
np "github.com/tech/sendico/pkg/messaging/notifications/processor"
handler "github.com/tech/sendico/pkg/messaging/notifications/site/handler"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
)
type DemoRequestProcessor struct {
logger mlogger.Logger
handler handler.DemoRequestHandler
event model.NotificationEvent
}
func (drp *DemoRequestProcessor) Process(ctx context.Context, envelope me.Envelope) error {
var msg gmessaging.DemoRequestEvent
if err := proto.Unmarshal(envelope.GetData(), &msg); err != nil {
drp.logger.Warn("Failed to decode demo request envelope", zap.Error(err), zap.String("topic", drp.event.ToString()))
return err
}
request := &model.DemoRequest{
Name: msg.GetName(),
OrganizationName: msg.GetOrganizationName(),
Phone: msg.GetPhone(),
WorkEmail: msg.GetWorkEmail(),
PayoutVolume: msg.GetPayoutVolume(),
Comment: msg.GetComment(),
}
return drp.handler(ctx, request)
}
func (drp *DemoRequestProcessor) GetSubject() model.NotificationEvent {
return drp.event
}
func NewDemoRequestProcessor(logger mlogger.Logger, handler handler.DemoRequestHandler) np.EnvelopeProcessor {
return &DemoRequestProcessor{
logger: logger.Named("demo_request_processor"),
handler: handler,
event: internalsite.NewDemoRequestEvent(),
}
}

View File

@@ -0,0 +1,53 @@
package model
import (
"strings"
"github.com/tech/sendico/pkg/merrors"
)
// DemoRequest represents a request submitted from the marketing site to request a demo.
type DemoRequest struct {
Name string `json:"name"`
OrganizationName string `json:"organizationName"`
Phone string `json:"phone"`
WorkEmail string `json:"workEmail"`
PayoutVolume string `json:"payoutVolume"`
Comment string `json:"comment,omitempty"`
}
// Normalize trims whitespace from all string fields.
func (dr *DemoRequest) Normalize() {
if dr == nil {
return
}
dr.Name = strings.TrimSpace(dr.Name)
dr.OrganizationName = strings.TrimSpace(dr.OrganizationName)
dr.Phone = strings.TrimSpace(dr.Phone)
dr.WorkEmail = strings.TrimSpace(dr.WorkEmail)
dr.PayoutVolume = strings.TrimSpace(dr.PayoutVolume)
dr.Comment = strings.TrimSpace(dr.Comment)
}
// Validate ensures that all required fields are present.
func (dr *DemoRequest) Validate() error {
if dr == nil {
return merrors.InvalidArgument("request payload is empty")
}
if dr.Name == "" {
return merrors.InvalidArgument("name must not be empty")
}
if dr.OrganizationName == "" {
return merrors.InvalidArgument("organization name must not be empty")
}
if dr.Phone == "" {
return merrors.InvalidArgument("phone must not be empty")
}
if dr.WorkEmail == "" {
return merrors.InvalidArgument("work email must not be empty")
}
if dr.PayoutVolume == "" {
return merrors.InvalidArgument("payout volume must not be empty")
}
return nil
}

View File

@@ -0,0 +1,31 @@
package model
import "testing"
func TestDemoRequestNormalizeAndValidate(t *testing.T) {
req := &DemoRequest{
Name: " Alice ",
OrganizationName: " Sendico ",
Phone: " +1 234 ",
WorkEmail: " demo@sendico.io ",
PayoutVolume: " 100k ",
Comment: " Excited ",
}
req.Normalize()
if err := req.Validate(); err != nil {
t.Fatalf("expected request to be valid, got error: %v", err)
}
if req.Name != "Alice" || req.OrganizationName != "Sendico" || req.Phone != "+1 234" || req.WorkEmail != "demo@sendico.io" || req.PayoutVolume != "100k" || req.Comment != "Excited" {
t.Fatalf("normalize failed: %+v", req)
}
}
func TestDemoRequestValidateMissing(t *testing.T) {
req := &DemoRequest{}
req.Normalize()
if err := req.Validate(); err == nil {
t.Fatalf("expected validation error for empty request")
}
}

View File

@@ -7,6 +7,7 @@ type Type = string
const (
Accounts Type = "accounts" // Represents user accounts in the system
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
Site Type = "site" // Represents public site endpoints
Automations Type = "automation" // Represents automation workflows
Changes Type = "changes" // Tracks changes made to resources
Clients Type = "clients" // Represents client information
@@ -59,7 +60,7 @@ const (
func StringToSType(s string) (Type, error) {
switch Type(s) {
case Accounts, Amplitude, Automations, Changes, Clients, Comments, ChainGateway, ChainWallets, ChainWalletBalances,
case Accounts, Amplitude, Site, Automations, Changes, Clients, Comments, ChainGateway, ChainWallets, ChainWalletBalances,
ChainTransfers, ChainDeposits, FXOracle, FeePlans, FilterProjects, Invitations, Invoices, Logo, Ledger,
LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications,
Organizations, Payments, PaymentOrchestrator, Permissions, Policies, PolicyAssignements, Priorities,

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
option go_package = "github.com/tech/sendico/pkg/generated/gmessaging";
message DemoRequestEvent {
string Name = 1;
string OrganizationName = 2;
string Phone = 3;
string WorkEmail = 4;
string PayoutVolume = 5;
string Comment = 6;
}

View File

@@ -0,0 +1,11 @@
package site
import (
"github.com/tech/sendico/pkg/mservice"
eapi "github.com/tech/sendico/server/interface/api"
"github.com/tech/sendico/server/internal/server/siteimp"
)
func Create(a eapi.API) (mservice.MicroService, error) {
return siteimp.CreateAPI(a)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/tech/sendico/server/interface/services/logo"
"github.com/tech/sendico/server/interface/services/organization"
"github.com/tech/sendico/server/interface/services/permission"
"github.com/tech/sendico/server/interface/services/site"
"go.uber.org/zap"
)
@@ -79,6 +80,7 @@ func (a *APIImp) installServices() error {
srvf = append(srvf, invitation.Create)
srvf = append(srvf, logo.Create)
srvf = append(srvf, permission.Create)
srvf = append(srvf, site.Create)
for _, v := range srvf {
if err := a.addMicroservice(v); err != nil {

View File

@@ -0,0 +1,60 @@
package siteimp
import (
"context"
"encoding/json"
"net/http"
api "github.com/tech/sendico/pkg/api/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/messaging"
snotifications "github.com/tech/sendico/pkg/messaging/notifications/site"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice"
eapi "github.com/tech/sendico/server/interface/api"
"go.uber.org/zap"
)
type SiteAPI struct {
logger mlogger.Logger
producer messaging.Producer
}
func (a *SiteAPI) Name() mservice.Type {
return mservice.Site
}
func (a *SiteAPI) Finish(_ context.Context) error {
return nil
}
func (a *SiteAPI) demoRequest(r *http.Request) http.HandlerFunc {
var request model.DemoRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
a.logger.Warn("Failed to decode demo request payload", zap.Error(err))
return response.BadRequest(a.logger, a.Name(), "invalid_payload", "Failed to decode demo request payload")
}
request.Normalize()
if err := request.Validate(); err != nil {
a.logger.Warn("Demo request validation failed", zap.Error(err))
return response.BadPayload(a.logger, a.Name(), err)
}
if err := a.producer.SendMessage(snotifications.DemoRequest(a.Name(), &request)); err != nil {
a.logger.Warn("Failed to enqueue demo request notification", zap.Error(err))
return response.Internal(a.logger, a.Name(), err)
}
return response.Accepted(a.logger, map[string]string{"status": "queued"})
}
func CreateAPI(a eapi.API) (*SiteAPI, error) {
p := &SiteAPI{
logger: a.Logger().Named(mservice.Site),
producer: a.Register().Messaging().Producer(),
}
a.Register().Handler(mservice.Site, "/demo/request", api.Post, p.demoRequest)
return p, nil
}

View File

@@ -15,9 +15,9 @@ PERMISSION_IS_FILTERED=false
AMPLI_ENVIRONMENT=production
API_PROTOCOL=https
SERVICE_HOST=app.sendico.io
API_ENDPOINT=https://app.sendico.io/api
API_ENDPOINT=/api/v1
WS_PROTOCOL=wss
WS_ENDPOINT=wss://app.sendico.io/ws
WS_ENDPOINT=/ws
AMPLITUDE_SECRET=c3d75b3e2520d708440acbb16b923e79
DEFAULT_LOCALE=en
DEFAULT_CURRENCY=EUR

View File

@@ -31,6 +31,9 @@ services:
NATS_URL: ${NATS_URL}
MAIL_USER: ${MAIL_USER}
MAIL_SECRET: ${MAIL_SECRET}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID}
TELEGRAM_THREAD_ID: ${TELEGRAM_THREAD_ID}
MONGO_HOST: ${MONGO_HOST}
MONGO_PORT: ${MONGO_PORT}
MONGO_DATABASE: ${MONGO_DATABASE}

View File

@@ -24,6 +24,8 @@ REQUIRED_SECRETS=(
NATS_USER
NATS_PASSWORD
NATS_URL
TELEGRAM_BOT_TOKEN
TELEGRAM_CHAT_ID
)
for var in "${REQUIRED_SECRETS[@]}"; do
@@ -50,6 +52,9 @@ API_ENDPOINT_SECRET_B64="$(b64enc "${API_ENDPOINT_SECRET}")"
NATS_USER_B64="$(b64enc "${NATS_USER}")"
NATS_PASSWORD_B64="$(b64enc "${NATS_PASSWORD}")"
NATS_URL_B64="$(b64enc "${NATS_URL}")"
TELEGRAM_BOT_TOKEN_B64="$(b64enc "${TELEGRAM_BOT_TOKEN}")"
TELEGRAM_CHAT_ID_B64="$(b64enc "${TELEGRAM_CHAT_ID}")"
TELEGRAM_THREAD_ID_B64="$(b64enc "${TELEGRAM_THREAD_ID:-}")"
SSH_OPTS=(
-i /root/.ssh/id_rsa
@@ -86,6 +91,9 @@ ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" \
NATS_USER_B64="$NATS_USER_B64" \
NATS_PASSWORD_B64="$NATS_PASSWORD_B64" \
NATS_URL_B64="$NATS_URL_B64" \
TELEGRAM_BOT_TOKEN_B64="$TELEGRAM_BOT_TOKEN_B64" \
TELEGRAM_CHAT_ID_B64="$TELEGRAM_CHAT_ID_B64" \
TELEGRAM_THREAD_ID_B64="$TELEGRAM_THREAD_ID_B64" \
bash -s <<'EOSSH'
set -euo pipefail
cd "${REMOTE_DIR}/compose"
@@ -135,10 +143,14 @@ API_ENDPOINT_SECRET="$(decode_b64 "$API_ENDPOINT_SECRET_B64")"
NATS_USER="$(decode_b64 "$NATS_USER_B64")"
NATS_PASSWORD="$(decode_b64 "$NATS_PASSWORD_B64")"
NATS_URL="$(decode_b64 "$NATS_URL_B64")"
TELEGRAM_BOT_TOKEN="$(decode_b64 "$TELEGRAM_BOT_TOKEN_B64")"
TELEGRAM_CHAT_ID="$(decode_b64 "$TELEGRAM_CHAT_ID_B64")"
TELEGRAM_THREAD_ID="$(decode_b64 "$TELEGRAM_THREAD_ID_B64")"
export MONGO_USER MONGO_PASSWORD
export MAIL_USER MAIL_SECRET API_ENDPOINT_SECRET
export NATS_USER NATS_PASSWORD NATS_URL
export TELEGRAM_BOT_TOKEN TELEGRAM_CHAT_ID TELEGRAM_THREAD_ID
COMPOSE_PROJECT_NAME="$COMPOSE_PROJECT"
export COMPOSE_PROJECT_NAME
read -r -a SERVICES <<<"${SERVICES_LINE}"

View File

@@ -4,7 +4,14 @@ set -eu
START_DIR="$(pwd)"
echo "[bump-version] invoked from ${START_DIR}"
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT=""
if command -v git >/dev/null 2>&1; then
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
fi
if [ -z "${REPO_ROOT}" ]; then
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
fi
echo "[bump-version] repo root resolved to ${REPO_ROOT}"
cd "${REPO_ROOT}"
@@ -60,4 +67,20 @@ if [ -z "${BRANCH}" ] || [ "${BRANCH}" = "HEAD" ]; then
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
fi
NETRC_MACHINE="${CI_NETRC_MACHINE:-${WOODPECKER_NETRC_MACHINE:-}}"
NETRC_USERNAME="${CI_NETRC_USERNAME:-${WOODPECKER_NETRC_USERNAME:-${CI_NETRC_LOGIN:-${WOODPECKER_NETRC_LOGIN:-}}}}"
NETRC_PASSWORD="${CI_NETRC_PASSWORD:-${WOODPECKER_NETRC_PASSWORD:-}}"
if [ -n "${NETRC_MACHINE}" ] && [ -n "${NETRC_USERNAME}" ] && [ -n "${NETRC_PASSWORD}" ]; then
NETRC_FILE="${HOME:-/root}/.netrc"
if [ ! -f "${NETRC_FILE}" ]; then
{
printf 'machine %s\n' "${NETRC_MACHINE}"
printf 'login %s\n' "${NETRC_USERNAME}"
printf 'password %s\n' "${NETRC_PASSWORD}"
} > "${NETRC_FILE}"
chmod 600 "${NETRC_FILE}"
echo "[bump-version] wrote credentials for ${NETRC_MACHINE}"
fi
fi
git push origin "HEAD:${BRANCH}"

View File

@@ -49,6 +49,7 @@ load_env_file ./.env.version
NOTIFICATION_MONGO_SECRET_PATH="${NOTIFICATION_MONGO_SECRET_PATH:?missing NOTIFICATION_MONGO_SECRET_PATH}"
NOTIFICATION_MAIL_SECRET_PATH="${NOTIFICATION_MAIL_SECRET_PATH:?missing NOTIFICATION_MAIL_SECRET_PATH}"
NOTIFICATION_API_SECRET_PATH="${NOTIFICATION_API_SECRET_PATH:?missing NOTIFICATION_API_SECRET_PATH}"
NOTIFICATION_TELEGRAM_SECRET_PATH="${NOTIFICATION_TELEGRAM_SECRET_PATH:?missing NOTIFICATION_TELEGRAM_SECRET_PATH}"
: "${NATS_HOST:?missing NATS_HOST}"
: "${NATS_PORT:?missing NATS_PORT}"
@@ -60,6 +61,14 @@ export MAIL_SECRET="$(./ci/vlt kv_get kv "${NOTIFICATION_MAIL_SECRET_PATH}" pass
export API_ENDPOINT_SECRET="$(./ci/vlt kv_get kv "${NOTIFICATION_API_SECRET_PATH}" secret)"
export TELEGRAM_BOT_TOKEN="$(./ci/vlt kv_get kv "${NOTIFICATION_TELEGRAM_SECRET_PATH}" bot_token)"
export TELEGRAM_CHAT_ID="$(./ci/vlt kv_get kv "${NOTIFICATION_TELEGRAM_SECRET_PATH}" chat_id)"
TELEGRAM_THREAD_ID=""
if TELEGRAM_THREAD_ID_VALUE="$(./ci/vlt kv_get kv "${NOTIFICATION_TELEGRAM_SECRET_PATH}" thread_id 2>/dev/null)"; then
TELEGRAM_THREAD_ID="$TELEGRAM_THREAD_ID_VALUE"
fi
export TELEGRAM_THREAD_ID
export NATS_USER="$(./ci/vlt kv_get kv sendico/nats user)"
export NATS_PASSWORD="$(./ci/vlt kv_get kv sendico/nats password)"
export NATS_URL="nats://${NATS_USER}:${NATS_PASSWORD}@${NATS_HOST}:${NATS_PORT}"