The provisioner records the MTProto channel id; the Bot API needs -100<id>. adapter-config now converts it, so the test channel resolves to -1004427430639. scripts/send-post.sh starts a short-lived adapter with the bot token injected from OpenBao (never printed) and sends one reviewed post. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5457@bnt-lap001 Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
97 lines
3.4 KiB
Go
97 lines
3.4 KiB
Go
// Package adaptercfg derives the adapter's configuration from the resolved
|
|
// presence and OpenBao.
|
|
//
|
|
// The adapter publishes. It holds a bot token with post_messages and a single
|
|
// chat id, and it has no MTProto session and no provisioning code path. That
|
|
// separation is the point of running two planes: a compromised adapter cannot
|
|
// create, rename, delete or re-permission anything.
|
|
package adaptercfg
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/tegwick/fluid-telegram/internal/secrets"
|
|
"github.com/tegwick/fluid-telegram/internal/spec"
|
|
"github.com/tegwick/fluid-telegram/internal/state"
|
|
)
|
|
|
|
// Config is what one adapter instance needs to publish to one channel.
|
|
type Config struct {
|
|
Campaign string
|
|
Channel string
|
|
ChatID int64
|
|
TokenRef string
|
|
}
|
|
|
|
// FromResolved maps the committed state onto one adapter instance. It never
|
|
// reads a secret: the token stays in OpenBao, and TokenRef is where to ask.
|
|
func FromResolved(rs *state.Resolved, channel, tokenRef string) (Config, error) {
|
|
if rs == nil || rs.Bot.ID == 0 {
|
|
return Config{}, fmt.Errorf("presence is not provisioned; run `provision apply` first")
|
|
}
|
|
switch channel {
|
|
case spec.Test, spec.Live:
|
|
case "":
|
|
return Config{}, fmt.Errorf("--channel is required (%q or %q)", spec.Test, spec.Live)
|
|
default:
|
|
return Config{}, fmt.Errorf("channel %q is not %q or %q", channel, spec.Test, spec.Live)
|
|
}
|
|
ch, ok := rs.Channels[channel]
|
|
if !ok {
|
|
if channel == spec.Live && !rs.TestChannelVerified(spec.Test) {
|
|
return Config{}, fmt.Errorf("the public channel is not provisioned yet; " +
|
|
"it is held until the test channel has a checked rendering")
|
|
}
|
|
return Config{}, fmt.Errorf("channel %q is not in the resolved state", channel)
|
|
}
|
|
if ch.ChatID == 0 {
|
|
return Config{}, fmt.Errorf("channel %q has no chat id in the resolved state", channel)
|
|
}
|
|
return Config{
|
|
Campaign: rs.Campaign,
|
|
Channel: channel,
|
|
ChatID: BotAPIChatID(ch.ChatID),
|
|
TokenRef: tokenRef,
|
|
}, nil
|
|
}
|
|
|
|
// BotAPIChatID converts a channel id as the provisioner records it (the
|
|
// MTProto channel id, positive) into the form the Bot API addresses it by
|
|
// (-100 followed by the id). An id that is already negative is in Bot API
|
|
// form and is returned unchanged.
|
|
func BotAPIChatID(id int64) int64 {
|
|
if id > 0 {
|
|
return -1000000000000 - id
|
|
}
|
|
return id
|
|
}
|
|
|
|
// EnvFile is the non-secret configuration an adapter process is started with.
|
|
// The token is named, not emitted: the runtime injects it from OpenBao.
|
|
func (c Config) EnvFile() string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "# adapter configuration for %s / %s\n", c.Campaign, c.Channel)
|
|
fmt.Fprintf(&b, "# derived from presence/resolved/%s.yaml; no secrets in this file\n", c.Campaign)
|
|
fmt.Fprintf(&b, "TELEGRAM_CHANNEL_ID=%d\n", c.ChatID)
|
|
fmt.Fprintf(&b, "TELEGRAM_BOT_TOKEN_REF=%s\n", c.TokenRef)
|
|
return b.String()
|
|
}
|
|
|
|
// LoadToken reads the bot token and nothing else. The operator session is a
|
|
// full-account credential; the adapter must never request it.
|
|
func LoadToken(ctx context.Context, store *secrets.Store) (string, error) {
|
|
fields, found, err := store.Get(ctx, secrets.KeyBotToken)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !found {
|
|
return "", fmt.Errorf("no bot token at %s; run `provision apply`", store.Ref(secrets.KeyBotToken))
|
|
}
|
|
token := fields["token"]
|
|
if token == "" {
|
|
return "", fmt.Errorf("bot token at %s is empty", store.Ref(secrets.KeyBotToken))
|
|
}
|
|
return token, nil
|
|
}
|