Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,11 @@ bridge config, database and python virtualenv (if applicable).

Note that deleting a bridge through the Beeper client settings will
*not* delete the bridge database that is stored locally; you must
delete that yourself, or use `bbctl delete` instead. The bridge
databases are stored in `~/.local/share/bbctl/prod` by default.
However, note that if you use any option that causes the bridge
database to be stored in a separate location, such as `-l` which
stores it in the current working directory, then `bbctl delete` will
*not* delete the bridge database, and you will again have to delete it
manually.

If you later re-add a self-hosted bridge after deleting it but not
deleting the local database, you should expect errors, as the bridge
will have been removed from Matrix rooms that it thinks it is a member
of.
delete that yourself, or use `bbctl delete` instead. (If you created
the bridge database with `bbctl run -l`, then run `bbctl delete -l`
from the same working directory to delete it.)

If you later re-add a self-hosted bridge after deleting it from the
Beeper servers but not deleting the local database, you should expect
errors, as the bridge will have been removed from Matrix rooms that it
thinks it is a member of.
60 changes: 57 additions & 3 deletions cmd/bbctl/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
Expand All @@ -23,6 +25,12 @@ var deleteCommand = &cli.Command{
Action: deleteBridge,
Before: RequiresAuth,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "local-dev",
Aliases: []string{"l"},
Usage: "Delete the bridge database and config from your current working directory. Useful for developing bridges.",
EnvVars: []string{"BEEPER_BRIDGE_LOCAL"},
},
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Expand All @@ -43,6 +51,18 @@ func deleteBridge(ctx *cli.Context) error {
} else if bridge == "hungryserv" {
return UserError{"You really shouldn't do that"}
}
var err error
dataDir := GetEnvConfig(ctx).BridgeDataDir
var bridgeDir string
localDev := ctx.Bool("local-dev")
if localDev {
bridgeDir, err = os.Getwd()
if err != nil {
return fmt.Errorf("failed to get working directory: %w", err)
}
} else {
bridgeDir = filepath.Join(dataDir, bridge)
}
Comment on lines +57 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Local-dev deletion can wipe unrelated data in the current directory.

--local-dev targets os.Getwd() and isLocalBridgeFile matches any config.yaml or *.db*. If the command is run from the wrong directory, unrelated config/SQLite files can be removed. Consider adding a stronger guard (e.g., validate the directory is a bbctl bridge dir or include the absolute path + file list in an explicit confirmation before deletion).

💡 Possible safety guard (example)
- err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("Are you sure you want to permanently delete %s?", bridge)}, &confirmation)
+ confirmMsg := fmt.Sprintf("Are you sure you want to permanently delete %s?", bridge)
+ if localDev {
+   confirmMsg = fmt.Sprintf(
+     "Are you sure you want to permanently delete %s and remove local data in %s?",
+     bridge, bridgeDir,
+   )
+ }
+ err = survey.AskOne(&survey.Confirm{Message: confirmMsg}, &confirmation)

Also applies to: 103-133

🤖 Prompt for AI Agents
In `@cmd/bbctl/delete.go` around lines 57 - 65, The current --local-dev flow uses
os.Getwd() (localDev) and then deletes files matched by isLocalBridgeFile, which
can remove unrelated config/SQLite files if run from the wrong directory; update
the delete command (around localDev, os.Getwd(), and the isLocalBridgeFile
usage) to add a safety guard: verify the target directory is a bbctl bridge
directory (e.g., check for a specific marker/config file or expected bridge
files), and if verification fails require an explicit confirmation flag or
interactive confirmation that prints the absolute paths and file list to be
deleted before proceeding; ensure the code path that sets bridgeDir and the
subsequent deletion logic exits unless the verification/confirmation succeeds.

homeserver := ctx.String("homeserver")
accessToken := GetEnvConfig(ctx).AccessToken
if !ctx.Bool("force") {
Expand All @@ -60,7 +80,7 @@ func deleteBridge(ctx *cli.Context) error {
}

var confirmation bool
err := survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("Are you sure you want to permanently delete %s?", bridge)}, &confirmation)
err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("Are you sure you want to permanently delete %s?", bridge)}, &confirmation)
if err != nil {
return err
} else if !confirmation {
Expand All @@ -71,12 +91,46 @@ func deleteBridge(ctx *cli.Context) error {
return fmt.Errorf("error deleting bridge: %w", err)
}
fmt.Println("Started deleting bridge")
bridgeDir := filepath.Join(GetEnvConfig(ctx).BridgeDataDir, bridge)
err = os.RemoveAll(bridgeDir)
err = deleteLocalBridgeData(bridgeDir, !localDev)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Printf("Failed to delete [magenta]%s[reset]: [red]%v[reset]", bridgeDir, err)
} else {
log.Printf("Deleted local bridge data from [magenta]%s[reset]", bridgeDir)
}
return nil
}

func isLocalBridgeFile(name string) bool {
if name == "config.yaml" {
return true
}
if strings.HasSuffix(name, ".db") {
return true
}
if strings.HasSuffix(name, ".db-shm") {
return true
}
if strings.HasSuffix(name, ".db-wal") {
return true
}
return false
}

func deleteLocalBridgeData(bridgeDir string, deleteWholeDir bool) error {
if deleteWholeDir {
return os.RemoveAll(bridgeDir)
}
items, err := os.ReadDir(bridgeDir)
if err != nil {
return err
}
for _, item := range items {
if isLocalBridgeFile(item.Name()) {
err := os.Remove(path.Join(bridgeDir, item.Name()))
if err != nil {
return err
}
}
}
return nil
}