2022-12-06 20:20:27 +00:00
package main
2022-12-06 22:10:27 +00:00
import (
2022-12-11 02:45:47 +00:00
"crypto/rsa"
"crypto/x509"
"encoding/pem"
2022-12-06 22:10:27 +00:00
"flag"
"fmt"
2022-12-11 00:48:43 +00:00
"io/ioutil"
2022-12-06 22:10:27 +00:00
"log"
"net"
"net/http"
"os"
2022-12-06 22:25:45 +00:00
"os/signal"
2022-12-11 00:48:43 +00:00
"path/filepath"
2022-12-06 22:10:27 +00:00
"strconv"
2022-12-11 00:48:43 +00:00
"strings"
2022-12-06 22:25:45 +00:00
"time"
2022-12-06 22:10:27 +00:00
2025-03-21 14:47:52 -04:00
"github.com/go-i2p/onramp"
2022-12-11 00:48:43 +00:00
"github.com/google/uuid"
2025-03-21 14:47:52 -04:00
builder "github.com/go-i2p/newsgo/builder"
server "github.com/go-i2p/newsgo/server"
signer "github.com/go-i2p/newsgo/signer"
2022-12-06 22:10:27 +00:00
)
2022-12-11 02:45:47 +00:00
func loadPrivateKey ( path string ) ( * rsa . PrivateKey , error ) {
privPem , err := ioutil . ReadFile ( path )
if nil != err {
return nil , err
}
privDer , _ := pem . Decode ( privPem )
privKey , err := x509 . ParsePKCS1PrivateKey ( privDer . Bytes )
if nil != err {
return nil , err
}
return privKey , nil
}
func Sign ( xmlfeed string ) error {
sk , err := loadPrivateKey ( * signingkey )
if err != nil {
return err
}
signer := signer . NewsSigner {
SignerID : * signerid ,
SigningKey : sk ,
}
return signer . CreateSu3 ( xmlfeed )
}
2022-12-06 22:10:27 +00:00
var (
2022-12-11 00:48:43 +00:00
serve = flag . String ( "command" , "help" , "command to run(may be `serve`,`build`,`sign`, or `help`(default)" )
2022-12-06 22:10:27 +00:00
dir = flag . String ( "newsdir" , "build" , "directory to serve news from" )
statsfile = flag . String ( "statsfile" , "build/stats.json" , "file to store stats in" )
host = flag . String ( "host" , "127.0.0.1" , "host to serve on" )
port = flag . String ( "port" , "9696" , "port to serve on" )
2022-12-11 00:48:43 +00:00
i2p = flag . Bool ( "i2p" , isSamAround ( ) , "automatically co-host on an I2P service using SAMv3" )
2022-12-06 23:23:18 +00:00
tcp = flag . Bool ( "http" , true , "host on an HTTP service at host:port" )
2022-12-11 00:48:43 +00:00
//newsfile = flag.String("newsfile", "data/entries.html", "entries to pass to news generator. If passed a directory, all `entries.html` files in the directory will be processed")
newsfile = flag . String ( "newsfile" , "data" , "entries to pass to news generator. If passed a directory, all `entries.html` files in the directory will be processed" )
bloclist = flag . String ( "blocklist" , "data/blocklist.xml" , "block list file to pass to news generator" )
releasejson = flag . String ( "releasejson" , "data/releases.json" , "json file describing an update to pass to news generator" )
title = flag . String ( "feedtitle" , "I2P News" , "title to use for the RSS feed to pass to news generator" )
subtitle = flag . String ( "feedsubtitle" , "News feed, and router updates" , "subtitle to use for the RSS feed to pass to news generator" )
site = flag . String ( "feedsite" , "http://i2p-projekt.i2p" , "site for the RSS feed to pass to news generator" )
mainurl = flag . String ( "feedmain" , DefaultFeedURL ( ) , "Primary newsfeed for updates to pass to news generator" )
backupurl = flag . String ( "feedbackup" , "http://dn3tvalnjz432qkqsvpfdqrwpqkw3ye4n4i2uyfr4jexvo3sp5ka.b32.i2p/news/news.atom.xml" , "Backup newsfeed for updates to pass to news generator" )
urn = flag . String ( "feeduid" , uuid . New ( ) . String ( ) , "UUID to use for the RSS feed to pass to news generator" )
builddir = flag . String ( "builddir" , "build" , "Build directory to output feeds to." )
2022-12-11 02:45:47 +00:00
signerid = flag . String ( "signerid" , "null@example.i2p" , "ID to use when signing the news" )
signingkey = flag . String ( "signingkey" , "signing_key.pem" , "Path to a signing key" )
2022-12-06 22:10:27 +00:00
)
func validatePort ( s * string ) {
_ , err := strconv . Atoi ( * s )
if err != nil {
log . Println ( "Port is invalid" )
os . Exit ( 1 )
}
}
func validateCommand ( s * string ) string {
switch * s {
case "serve" :
return "serve"
case "build" :
return "build"
case "sign" :
return "sign"
default :
return "help"
}
}
func Help ( ) {
fmt . Println ( "newsgo" )
fmt . Println ( "======" )
fmt . Println ( "" )
2022-12-11 02:48:25 +00:00
fmt . Println ( "I2P News Server Tool/Library. A whole lot faster than the python one. Otherwise compatible." )
2022-12-06 22:10:27 +00:00
fmt . Println ( "" )
fmt . Println ( "Usage" )
fmt . Println ( "-----" )
fmt . Println ( "" )
fmt . Println ( "./newsgo -command $command -newsdir $news_directory -statsfile $news_stats_file" )
fmt . Println ( "" )
fmt . Println ( "### Commands" )
fmt . Println ( "" )
fmt . Println ( " - serve: Serve newsfeeds from a directory" )
2022-12-11 16:10:26 +00:00
fmt . Println ( " - build: Build newsfeeds from XML" )
fmt . Println ( " - sign: Sign newsfeeds with local keys" )
2022-12-06 22:10:27 +00:00
fmt . Println ( "" )
fmt . Println ( "### Options" )
fmt . Println ( "" )
2022-12-06 22:13:57 +00:00
fmt . Println ( "Use these options to configure the software" )
fmt . Println ( "" )
2022-12-11 00:48:43 +00:00
fmt . Println ( "#### Server Options(use with `serve`)" )
2022-12-06 22:13:57 +00:00
fmt . Println ( "" )
2022-12-06 22:10:27 +00:00
fmt . Println ( " - `-newsdir`: directory to serve newsfeed from" )
fmt . Println ( " - `-statsfile`: file to store the stats in, in json format" )
2022-12-06 22:13:57 +00:00
fmt . Println ( " - `-host`: host to serve news files on" )
fmt . Println ( " - `-port`: port to serve news files on" )
2022-12-11 00:55:21 +00:00
fmt . Println ( " - `-http`: serve news on host:port using HTTP" )
2022-12-06 22:13:57 +00:00
fmt . Println ( " - `-i2p`: serve news files directly to I2P using SAMv3" )
fmt . Println ( "" )
2022-12-11 00:48:43 +00:00
fmt . Println ( "#### Builder Options(use with `build`)" )
2022-12-06 22:13:57 +00:00
fmt . Println ( "" )
2022-12-11 00:48:43 +00:00
fmt . Println ( " - `-newsfile`: entries to pass to news generator. If passed a directory, all `entries.html` files in the directory will be processed" )
fmt . Println ( " - `-blockfile`: block list file to pass to news generator" )
fmt . Println ( " - `-releasejson`: json file describing an update to pass to news generator" )
fmt . Println ( " - `-feedtitle`: title to use for the RSS feed to pass to news generator" )
fmt . Println ( " - `-feedsubtitle`: subtitle to use for the RSS feed to pass to news generator" )
fmt . Println ( " - `-feedsite`: site for the RSS feed to pass to news generator" )
fmt . Println ( " - `-feedmain`: Primary newsfeed for updates to pass to news generator" )
fmt . Println ( " - `-feedbackup`: Backup newsfeed for updates to pass to news generator" )
fmt . Println ( " - `-feeduri`: UUID to use for the RSS feed to pass to news generator" )
2022-12-11 02:45:47 +00:00
fmt . Println ( " - `-builddir`: directory to output XML files in" )
2022-12-06 22:13:57 +00:00
fmt . Println ( "" )
2022-12-11 00:48:43 +00:00
fmt . Println ( "#### Signer Options(use with `sign`)" )
2022-12-06 22:10:27 +00:00
fmt . Println ( "" )
2022-12-11 02:45:47 +00:00
fmt . Println ( " - `-signerid`: ID of the news signer" )
fmt . Println ( " - `-signingkey`: path to the signing key" )
2022-12-06 22:10:27 +00:00
}
2022-12-06 20:20:27 +00:00
2022-12-06 23:18:16 +00:00
func Serve ( host , port string , s * server . NewsServer ) error {
2022-12-06 22:10:27 +00:00
ln , err := net . Listen ( "tcp" , net . JoinHostPort ( host , port ) )
if err != nil {
return err
}
2022-12-06 22:25:45 +00:00
return http . Serve ( ln , s )
}
2022-12-06 23:18:16 +00:00
func ServeI2P ( s * server . NewsServer ) error {
2022-12-06 22:25:45 +00:00
garlic := & onramp . Garlic { }
defer garlic . Close ( )
ln , err := garlic . Listen ( )
if err != nil {
return err
}
defer ln . Close ( )
2022-12-06 22:10:27 +00:00
return http . Serve ( ln , s )
}
2022-12-11 00:48:43 +00:00
func isSamAround ( ) bool {
ln , err := net . Listen ( "tcp" , "127.0.0.1:7656" )
if err != nil {
return true
}
ln . Close ( )
return false
}
func DefaultFeedURL ( ) string {
if ! isSamAround ( ) {
return "http://tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq.b32.i2p/news.atom.xml"
}
garlic := & onramp . Garlic { }
defer garlic . Close ( )
ln , err := garlic . Listen ( )
if err != nil {
return "http://tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq.b32.i2p/news.atom.xml"
}
defer ln . Close ( )
return "http://" + ln . Addr ( ) . String ( ) + "/news.atom.xml"
}
func Build ( newsFile string ) {
news := builder . Builder ( newsFile , * releasejson , * bloclist )
news . TITLE = * title
news . SITEURL = * site
news . MAINFEED = * mainurl
news . BACKUPFEED = * backupurl
news . SUBTITLE = * subtitle
news . URNID = * urn
2022-12-11 02:17:24 +00:00
base := filepath . Join ( * newsfile , "entries.html" )
if newsFile != base {
news . Feed . BaseEntriesHTMLPath = base
}
2022-12-11 00:48:43 +00:00
if feed , err := news . Build ( ) ; err != nil {
log . Printf ( "Build error: %s" , err )
} else {
2022-12-11 00:53:16 +00:00
filename := strings . Replace ( strings . Replace ( strings . Replace ( strings . Replace ( newsFile , ".html" , ".atom.xml" , - 1 ) , "entries." , "news_" , - 1 ) , "translations" , "" , - 1 ) , "news_atom" , "news.atom" , - 1 )
2022-12-11 00:48:43 +00:00
if err := os . MkdirAll ( filepath . Join ( * builddir , filepath . Dir ( filename ) ) , 0755 ) ; err != nil {
panic ( err )
}
if err = ioutil . WriteFile ( filepath . Join ( * builddir , filename ) , [ ] byte ( feed ) , 0644 ) ; err != nil {
panic ( err )
}
}
}
2022-12-06 22:10:27 +00:00
func main ( ) {
flag . Parse ( )
command := validateCommand ( serve )
validatePort ( port )
switch command {
case "serve" :
2022-12-06 23:18:16 +00:00
s := server . Serve ( * dir , * statsfile )
2022-12-06 23:23:18 +00:00
if * tcp {
go func ( ) {
if err := Serve ( * host , * port , s ) ; err != nil {
panic ( err )
}
} ( )
}
2022-12-06 22:25:45 +00:00
if * i2p {
go func ( ) {
2022-12-06 23:18:16 +00:00
if err := ServeI2P ( s ) ; err != nil {
2022-12-06 22:25:45 +00:00
panic ( err )
}
} ( )
}
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
go func ( ) {
for sig := range c {
log . Println ( "captured: " , sig )
2022-12-06 23:18:16 +00:00
s . Stats . Save ( )
2022-12-06 22:25:45 +00:00
os . Exit ( 0 )
}
} ( )
i := 0
for {
time . Sleep ( time . Minute )
2022-12-06 23:18:16 +00:00
log . Printf ( "Running for %d minutes." , i )
2022-12-06 22:25:45 +00:00
i ++
2022-12-06 22:10:27 +00:00
}
case "build" :
2022-12-11 00:48:43 +00:00
f , e := os . Stat ( * newsfile )
if e != nil {
panic ( e )
}
if f . IsDir ( ) {
err := filepath . Walk ( * newsfile ,
func ( path string , info os . FileInfo , err error ) error {
if err != nil {
return err
}
ext := filepath . Ext ( path )
if ext == ".html" {
Build ( path )
}
return nil
} )
if err != nil {
log . Println ( err )
}
} else {
Build ( * newsfile )
}
2022-12-06 22:10:27 +00:00
case "sign" :
2022-12-11 02:45:47 +00:00
f , e := os . Stat ( * newsfile )
if e != nil {
panic ( e )
}
if f . IsDir ( ) {
err := filepath . Walk ( * newsfile ,
func ( path string , info os . FileInfo , err error ) error {
if err != nil {
return err
}
ext := filepath . Ext ( path )
if ext == ".html" {
2022-12-11 02:46:53 +00:00
Sign ( path )
2022-12-11 02:45:47 +00:00
}
return nil
} )
if err != nil {
log . Println ( err )
}
} else {
2022-12-11 02:46:53 +00:00
Sign ( * newsfile )
2022-12-11 02:45:47 +00:00
}
2022-12-06 22:10:27 +00:00
case "help" :
Help ( )
default :
Help ( )
}
2022-12-06 20:20:27 +00:00
}