Rename to uncensored-send
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
/send
|
/uncensored-send
|
||||||
/data/
|
/data/
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ Built for sharing large binaries with friends so that I don't have to manually m
|
|||||||
|
|
||||||
```
|
```
|
||||||
go build
|
go build
|
||||||
./send token add me --vanity --admin
|
./uncensored-send token add me --vanity --admin
|
||||||
./send
|
./uncensored-send
|
||||||
```
|
```
|
||||||
|
|
||||||
Open <http://localhost:8080>, click **Log in**, paste the token. That's it.
|
Open <http://localhost:8080>, click **Log in**, paste the token. That's it.
|
||||||
@@ -25,9 +25,9 @@ upload limit.
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
**Read `./send --help` rather than this file.**
|
**Read `./uncensored-send --help` rather than this file.**
|
||||||
|
|
||||||
Every option also reads from `SEND_`-prefixed environment variables.
|
Every option also reads from `UNCENSORED_SEND_`-prefixed environment variables.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tokenFilePerm is deliberately stricter than the rest of the data directory:
|
// tokenFilePerm is deliberately stricter than the rest of the data directory:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaults() *config.Config {
|
func defaults() *config.Config {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type Config struct {
|
|||||||
trustedProxyNets []*net.IPNet
|
trustedProxyNets []*net.IPNet
|
||||||
}
|
}
|
||||||
|
|
||||||
const EnvPrefix = "SEND_"
|
const EnvPrefix = "UNCENSORED_SEND_"
|
||||||
|
|
||||||
// Register wires every option onto s. Short forms exist only for the options
|
// Register wires every option onto s. Short forms exist only for the options
|
||||||
// reached often; everything else is long-only, by design.
|
// reached often; everything else is long-only, by design.
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func TestFormatRoundTrips(t *testing.T) {
|
|||||||
func TestHyphenConvention(t *testing.T) {
|
func TestHyphenConvention(t *testing.T) {
|
||||||
newSet := func() (*Set, *string) {
|
newSet := func() (*Set, *string) {
|
||||||
var listen string
|
var listen string
|
||||||
s := NewSet("test", "SEND_TEST_")
|
s := NewSet("test", "UNCENSORED_SEND_TEST_")
|
||||||
s.String(&listen, "listen", "l", "default", "ADDR", "listen address")
|
s.String(&listen, "listen", "l", "default", "ADDR", "listen address")
|
||||||
return s, &listen
|
return s, &listen
|
||||||
}
|
}
|
||||||
@@ -116,9 +116,9 @@ func TestHyphenConvention(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvFallback(t *testing.T) {
|
func TestEnvFallback(t *testing.T) {
|
||||||
t.Setenv("SEND_TEST_MAX_SIZE", "4GiB")
|
t.Setenv("UNCENSORED_SEND_TEST_MAX_SIZE", "4GiB")
|
||||||
var size int64
|
var size int64
|
||||||
s := NewSet("test", "SEND_TEST_")
|
s := NewSet("test", "UNCENSORED_SEND_TEST_")
|
||||||
s.Size(&size, "max-size", "s", "2GiB", "cap")
|
s.Size(&size, "max-size", "s", "2GiB", "cap")
|
||||||
if err := s.Parse(nil); err != nil {
|
if err := s.Parse(nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -128,7 +128,7 @@ func TestEnvFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// An explicit flag still wins over the environment.
|
// An explicit flag still wins over the environment.
|
||||||
s = NewSet("test", "SEND_TEST_")
|
s = NewSet("test", "UNCENSORED_SEND_TEST_")
|
||||||
s.Size(&size, "max-size", "s", "2GiB", "cap")
|
s.Size(&size, "max-size", "s", "2GiB", "cap")
|
||||||
if err := s.Parse([]string{"--max-size", "1GiB"}); err != nil {
|
if err := s.Parse([]string{"--max-size", "1GiB"}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (s *Set) note(long, short string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// envName maps --max-size to SEND_MAX_SIZE.
|
// envName maps --max-size to UNCENSORED_SEND_MAX_SIZE.
|
||||||
func (s *Set) envName(long string) string {
|
func (s *Set) envName(long string) string {
|
||||||
return s.envPrefix + strings.ToUpper(strings.ReplaceAll(long, "-", "_"))
|
return s.envPrefix + strings.ToUpper(strings.ReplaceAll(long, "-", "_"))
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ func (s *Set) checkConvention(args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyEnv seeds every option from its SEND_-prefixed environment variable.
|
// applyEnv seeds every option from its UNCENSORED_SEND_-prefixed environment variable.
|
||||||
// Called before parsing so an explicit command-line flag always wins.
|
// Called before parsing so an explicit command-line flag always wins.
|
||||||
func (s *Set) applyEnv() error {
|
func (s *Set) applyEnv() error {
|
||||||
for _, sp := range s.specs {
|
for _, sp := range s.specs {
|
||||||
@@ -214,7 +214,7 @@ func (s *Set) Parse(args []string) error {
|
|||||||
// Changed reports whether an option was given on the command line.
|
// Changed reports whether an option was given on the command line.
|
||||||
//
|
//
|
||||||
// Values picked up from the environment do not count: a command that acts only
|
// Values picked up from the environment do not count: a command that acts only
|
||||||
// on the options you actually typed must not quietly act on a SEND_ variable
|
// on the options you actually typed must not quietly act on a UNCENSORED_SEND_ variable
|
||||||
// exported for the server.
|
// exported for the server.
|
||||||
func (s *Set) Changed(long string) bool {
|
func (s *Set) Changed(long string) bool {
|
||||||
if s.fromEnv[long] {
|
if s.fromEnv[long] {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminPage is the one view that shows every object, regardless of who
|
// adminPage is the one view that shows every object, regardless of who
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// reason to prefer it over localStorage, where any injected script could
|
// reason to prefer it over localStorage, where any injected script could
|
||||||
// exfiltrate the credential. The page never needs to see the value: the server
|
// exfiltrate the credential. The page never needs to see the value: the server
|
||||||
// resolves it and renders who the caller is.
|
// resolves it and renders who the caller is.
|
||||||
const tokenCookie = "send_token"
|
const tokenCookie = "uncensored_send_token"
|
||||||
|
|
||||||
// rememberFor is how long a persisted login survives. Tokens are revoked by
|
// rememberFor is how long a persisted login survives. Tokens are revoked by
|
||||||
// deleting them from the token file, so a long window costs nothing.
|
// deleting them from the token file, so a long window costs nothing.
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleDelete removes an object early. Three credentials are accepted: the
|
// handleDelete removes an object early. Three credentials are accepted: the
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// downloadCSP is as inert as a policy gets. Combined with the attachment
|
// downloadCSP is as inert as a policy gets. Combined with the attachment
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loginPage backs both the form and its error redisplay.
|
// loginPage backs both the form and its error redisplay.
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeJSON(w http.ResponseWriter, status int, v any) {
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// limiter is a per-key token bucket. It is deliberately small: there is no
|
// limiter is a per-key token bucket. It is deliberately small: there is no
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
"send/web"
|
"uncensored-send/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server holds everything the handlers need. It is safe for concurrent use.
|
// Server holds everything the handlers need. It is safe for concurrent use.
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// clock is the fixed instant tests start from; s.now is swapped so expiry can
|
// clock is the fixed instant tests start from; s.now is swapped so expiry can
|
||||||
@@ -42,7 +42,7 @@ func newHarness(t *testing.T, tweak func(*config.Config)) *harness {
|
|||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
cfg := config.Config{}
|
cfg := config.Config{}
|
||||||
fs := config.NewSet("test", "SEND_TEST_")
|
fs := config.NewSet("test", "UNCENSORED_SEND_TEST_")
|
||||||
cfg.Register(fs)
|
cfg.Register(fs)
|
||||||
if err := fs.Parse(nil); err != nil {
|
if err := fs.Parse(nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Command send is a small self-hosted file drop: uploads land in a flat data
|
// Command uncensored-send is a small self-hosted file drop: uploads land in a flat data
|
||||||
// directory, expire on their own, and are served back as inert attachments.
|
// directory, expire on their own, and are served back as inert attachments.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@@ -15,10 +15,10 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
"send/internal/server"
|
"uncensored-send/internal/server"
|
||||||
"send/internal/store"
|
"uncensored-send/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -26,7 +26,7 @@ func main() {
|
|||||||
if errors.Is(err, flag.ErrHelp) {
|
if errors.Is(err, flag.ErrHelp) {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "send: "+err.Error())
|
fmt.Fprintln(os.Stderr, "uncensored-send: "+err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,13 +38,13 @@ func run(args []string) error {
|
|||||||
return serve(args)
|
return serve(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
const serveUsage = `send - a small self-hosted file drop
|
const serveUsage = `uncensored-send - a small self-hosted file drop
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
send [options] run the server
|
uncensored-send [options] run the server
|
||||||
send token add <name> [opts] mint a token, printing it once
|
uncensored-send token add <name> [opts] mint a token, printing it once
|
||||||
send token list list tokens
|
uncensored-send token list list tokens
|
||||||
send token rm <name> remove a token
|
uncensored-send token rm <name> remove a token
|
||||||
|
|
||||||
Options take one hyphen with a single letter (-s) and two with a full
|
Options take one hyphen with a single letter (-s) and two with a full
|
||||||
word (--max-size). Every option can also be set from the environment.
|
word (--max-size). Every option can also be set from the environment.
|
||||||
@@ -54,7 +54,7 @@ Options:
|
|||||||
|
|
||||||
func serve(args []string) error {
|
func serve(args []string) error {
|
||||||
var cfg config.Config
|
var cfg config.Config
|
||||||
fs := config.NewSet("send", config.EnvPrefix)
|
fs := config.NewSet("uncensored-send", config.EnvPrefix)
|
||||||
fs.SetOutput(os.Stderr)
|
fs.SetOutput(os.Stderr)
|
||||||
cfg.Register(fs)
|
cfg.Register(fs)
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"send/internal/auth"
|
"uncensored-send/internal/auth"
|
||||||
"send/internal/config"
|
"uncensored-send/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tokenUsage = `send token - manage upload credentials
|
const tokenUsage = `uncensored-send token - manage upload credentials
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
send token add <name> [options] generate a token
|
uncensored-send token add <name> [options] generate a token
|
||||||
send token add <name> --token - read a chosen one from stdin
|
uncensored-send token add <name> --token - read a chosen one from stdin
|
||||||
send token rotate <name> [--token -] replace the secret, keep everything else
|
uncensored-send token rotate <name> [--token -] replace the secret, keep everything else
|
||||||
send token set <name> [options] change limits and flags in place
|
uncensored-send token set <name> [options] change limits and flags in place
|
||||||
send token list [options]
|
uncensored-send token list [options]
|
||||||
send token rm <name> [options]
|
uncensored-send token rm <name> [options]
|
||||||
|
|
||||||
A token grants its own size and lifetime limits. Any limit left unset is
|
A token grants its own size and lifetime limits. Any limit left unset is
|
||||||
inherited from the running server's defaults, so a token with no options
|
inherited from the running server's defaults, so a token with no options
|
||||||
@@ -136,7 +136,7 @@ type tokenOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *tokenOptions) register() *config.Set {
|
func (o *tokenOptions) register() *config.Set {
|
||||||
fs := config.NewSet("send token", config.EnvPrefix)
|
fs := config.NewSet("uncensored-send token", config.EnvPrefix)
|
||||||
fs.String(&o.dataDir, "data", "d", "./data", "DIR", "directory holding the data")
|
fs.String(&o.dataDir, "data", "d", "./data", "DIR", "directory holding the data")
|
||||||
fs.String(&o.tokensPath, "tokens", "", "", "FILE", "token file location (default <data>/tokens.json)")
|
fs.String(&o.tokensPath, "tokens", "", "", "FILE", "token file location (default <data>/tokens.json)")
|
||||||
fs.String(&o.maxSize, "max-size", "s", "", "SIZE", "per-upload cap for this token; 'unlimited' to remove it")
|
fs.String(&o.maxSize, "max-size", "s", "", "SIZE", "per-upload cap for this token; 'unlimited' to remove it")
|
||||||
@@ -296,7 +296,7 @@ func tokenSet(file *auth.File, name string, opts tokenOptions, fs *config.Set) e
|
|||||||
changes = append(changes, fmt.Sprintf("--%s=%t", f.flag, f.value))
|
changes = append(changes, fmt.Sprintf("--%s=%t", f.flag, f.value))
|
||||||
}
|
}
|
||||||
if fs.Changed("token") {
|
if fs.Changed("token") {
|
||||||
return errors.New("use \"send token rotate\" to change the secret")
|
return errors.New("use \"uncensored-send token rotate\" to change the secret")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<section class="card">
|
<section class="card">
|
||||||
<h2>Tokens</h2>
|
<h2>Tokens</h2>
|
||||||
<p class="hint">
|
<p class="hint">
|
||||||
Tokens are minted, rotated and revoked with <code>./send token</code> on the
|
Tokens are minted, rotated and revoked with <code>./uncensored-send token</code> on the
|
||||||
server. They are deliberately not manageable from here: nothing that hands
|
server. They are deliberately not manageable from here: nothing that hands
|
||||||
out credentials should be reachable over the network.
|
out credentials should be reachable over the network.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user