all repos — honk @ 520d4ed221e7eb64f999288699c7920c7b26a057

my fork of honk

main.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"flag"
 20	"fmt"
 21	"html/template"
 22	golog "log"
 23	"log/syslog"
 24	notrand "math/rand"
 25	"os"
 26	"runtime/pprof"
 27	"strconv"
 28	"time"
 29
 30	"humungus.tedunangst.com/r/webs/log"
 31)
 32
 33var softwareVersion = "develop"
 34
 35func init() {
 36	notrand.Seed(time.Now().Unix())
 37}
 38
 39var serverName string
 40var serverPrefix string
 41var masqName string
 42var dataDir = "."
 43var viewDir = "."
 44var iconName = "icon.png"
 45var serverMsg template.HTML
 46var aboutMsg template.HTML
 47var loginMsg template.HTML
 48
 49func serverURL(u string, args ...interface{}) string {
 50	return fmt.Sprintf("https://"+serverName+u, args...)
 51}
 52
 53func ElaborateUnitTests() {
 54	user, _ := butwhatabout("test")
 55	syndicate(user, "https://mastodon.social/tags/mastoadmin.rss")
 56}
 57
 58func unplugserver(hostname string) {
 59	db := opendatabase()
 60	xid := fmt.Sprintf("https://%s", hostname)
 61	db.Exec("delete from honkers where xid = ? and flavor = 'dub'", xid)
 62	db.Exec("delete from doovers where rcpt = ?", xid)
 63	xid += "/%"
 64	db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid)
 65	db.Exec("delete from doovers where rcpt like ?", xid)
 66}
 67
 68func reexecArgs(cmd string) []string {
 69	args := []string{"-datadir", dataDir}
 70	args = append(args, log.Args()...)
 71	args = append(args, cmd)
 72	return args
 73}
 74
 75var elog, ilog, dlog *golog.Logger
 76
 77func errx(msg string, args ...interface{}) {
 78	fmt.Fprintf(os.Stderr, msg+"\n", args...)
 79	os.Exit(1)
 80}
 81
 82var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
 83var memprofile = flag.String("memprofile", "", "write memory profile to this file")
 84var memprofilefd *os.File
 85
 86func main() {
 87	flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
 88	flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
 89	flag.Parse()
 90	if *cpuprofile != "" {
 91		f, err := os.Create(*cpuprofile)
 92		if err != nil {
 93			errx("can't open cpu profile: %s", err)
 94		}
 95		pprof.StartCPUProfile(f)
 96	}
 97	if *memprofile != "" {
 98		f, err := os.Create(*memprofile)
 99		if err != nil {
100			errx("can't open mem profile: %s", err)
101		}
102		memprofilefd = f
103	}
104
105	log.Init(log.Options{Progname: "honk", Facility: syslog.LOG_UUCP})
106	elog = log.E
107	ilog = log.I
108	dlog = log.D
109
110	if os.Geteuid() == 0 {
111		elog.Fatalf("do not run honk as root")
112	}
113
114	args := flag.Args()
115	cmd := "run"
116	if len(args) > 0 {
117		cmd = args[0]
118	}
119	switch cmd {
120	case "init":
121		initdb()
122	case "upgrade":
123		upgradedb()
124	case "version":
125		fmt.Println(softwareVersion)
126		os.Exit(0)
127	}
128	db := opendatabase()
129	dbversion := 0
130	getconfig("dbversion", &dbversion)
131	if dbversion != myVersion {
132		elog.Fatal("incorrect database version. run upgrade.")
133	}
134	getconfig("servermsg", &serverMsg)
135	getconfig("aboutmsg", &aboutMsg)
136	getconfig("loginmsg", &loginMsg)
137	getconfig("servername", &serverName)
138	getconfig("masqname", &masqName)
139	if masqName == "" {
140		masqName = serverName
141	}
142	serverPrefix = serverURL("/")
143	getconfig("usersep", &userSep)
144	getconfig("honksep", &honkSep)
145	getconfig("devel", &develMode)
146	if develMode {
147		gogglesDoNothing()
148	}
149	getconfig("fasttimeout", &fastTimeout)
150	getconfig("slowtimeout", &slowTimeout)
151	getconfig("honkwindow", &honkwindow)
152	honkwindow *= 24 * time.Hour
153
154	prepareStatements(db)
155
156	dbx := opendatabasex()
157	if dbx == nil {
158		elog.Fatal("dbx is nil")
159	}
160	prepareStatementsx(dbx)
161
162	switch cmd {
163	case "admin":
164		adminscreen()
165	case "import":
166		if len(args) != 4 {
167			errx("import username honk|mastodon|twitter srcdir")
168		}
169		importMain(args[1], args[2], args[3])
170	case "export":
171		if len(args) != 3 {
172			errx("export username destdir")
173		}
174		export(args[1], args[2])
175	case "devel":
176		if len(args) != 2 {
177			errx("need an argument: devel (on|off)")
178		}
179		switch args[1] {
180		case "on":
181			setconfig("devel", 1)
182		case "off":
183			setconfig("devel", 0)
184		default:
185			errx("argument must be on or off")
186		}
187	case "setconfig":
188		if len(args) != 3 {
189			errx("need an argument: setconfig key val")
190		}
191		var val interface{}
192		var err error
193		if val, err = strconv.Atoi(args[2]); err != nil {
194			val = args[2]
195		}
196		setconfig(args[1], val)
197	case "adduser":
198		adduser()
199	case "deluser":
200		if len(args) < 2 {
201			errx("usage: honk deluser username")
202		}
203		deluser(args[1])
204	case "chpass":
205		if len(args) < 2 {
206			errx("usage: honk chpass username")
207		}
208		chpass(args[1])
209	case "follow":
210		if len(args) < 3 {
211			errx("usage: honk follow username url")
212		}
213		user, err := butwhatabout(args[1])
214		if err != nil {
215			errx("user %s not found", args[1])
216		}
217		var meta HonkerMeta
218		mj, _ := jsonify(&meta)
219		honkerid, flavor, err := savehonker(user, args[2], "", "presub", "", mj)
220		if err != nil {
221			errx("had some trouble with that: %s", err)
222		}
223		if flavor == "presub" {
224			followyou(user, honkerid, true)
225		}
226	case "unfollow":
227		if len(args) < 3 {
228			errx("usage: honk unfollow username url")
229		}
230		user, err := butwhatabout(args[1])
231		if err != nil {
232			errx("user not found")
233		}
234		row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
235		var honkerid int64
236		err = row.Scan(&honkerid)
237		if err != nil {
238			errx("sorry couldn't find them")
239		}
240		unfollowyou(user, honkerid, true)
241	case "sendmsg":
242		if len(args) < 4 {
243			errx("usage: honk send username filename rcpt")
244		}
245		user, err := butwhatabout(args[1])
246		if err != nil {
247			errx("user %s not found", args[1])
248		}
249		data, err := os.ReadFile(args[2])
250		if err != nil {
251			errx("can't read file: %s", err)
252		}
253		deliverate(user.ID, args[3], data)
254	case "cleanup":
255		arg := "30"
256		if len(args) > 1 {
257			arg = args[1]
258		}
259		cleanupdb(arg)
260	case "unplug":
261		if len(args) < 2 {
262			errx("usage: honk unplug servername")
263		}
264		name := args[1]
265		unplugserver(name)
266	case "backup":
267		if len(args) < 2 {
268			errx("usage: honk backup dirname")
269		}
270		name := args[1]
271		svalbard(name)
272	case "ping":
273		if len(args) < 3 {
274			errx("usage: honk ping (from username) (to username or url)")
275		}
276		name := args[1]
277		targ := args[2]
278		user, err := butwhatabout(name)
279		if err != nil {
280			errx("unknown user %s", name)
281		}
282		ping(user, targ)
283	case "run":
284		serve()
285	case "backend":
286		backendServer()
287	case "test":
288		ElaborateUnitTests()
289	default:
290		errx("unknown command")
291	}
292}