add the ability to store keys in a separate directory.
This commit is contained in:
10
Makefile
10
Makefile
@ -9,15 +9,7 @@ build:
|
||||
go build -o ./i2p.plugin.native ./cmd/i2p.plugin.native
|
||||
|
||||
readme:
|
||||
echo "I2P native plugin generation tool" | tee README.md
|
||||
echo "=================================" | tee -a README.md
|
||||
echo "" | tee -a README.md
|
||||
echo "I wrote this way faster than I documented it. Shocking, right?" | tee -a README.md
|
||||
echo "" | tee -a README.md
|
||||
echo "This is a handy little tool for assembling I2P plugins when those" | tee -a README.md
|
||||
echo "plugins don't have a clean way to interface with the JVM, or just don't" | tee -a README.md
|
||||
echo "need one. Think of it a little like \`checkinstall\` but for I2P Plugins." | tee -a README.md
|
||||
echo "Right now it mostly works, and it's pretty cleanly put together." | tee -a README.md
|
||||
cat desc | tee README.md
|
||||
echo "" | tee -a README.md
|
||||
echo "There are some examples in the Makefile for now." | tee -a README.md
|
||||
echo "" | tee -a README.md
|
||||
|
26
README.md
26
README.md
@ -1,24 +1,14 @@
|
||||
ShellService Plugin Generator
|
||||
=============================
|
||||
|
||||
Generates a valid ShellService plugin from a single script or executable. A
|
||||
ShellService plugin is an application which runs as a process which is managed
|
||||
by the I2P sofware. This allows I2P to manage the lifetime of a non-JVM
|
||||
application by monitoring it. That way, plugins don't risk outliving the I2P
|
||||
router because the router loses track of them.
|
||||
Generates a valid ShellService plugin from a single script or executable. A ShellService plugin is an application which runs as a process which is managed by the I2P sofware. This allows I2P to manage the lifetime of a non-JVM application by monitoring it. That way, plugins don't risk outliving the I2P router because the router loses track of them.
|
||||
|
||||
A ShellService plugin should not "daemonize" itself or othewise fork itself to the
|
||||
background. The I2P router will manage it as a "Client Application" and daemonizing
|
||||
it will break this. If your process forks itself to the background by default, it
|
||||
must have this feature disabled to work as a ShellService.
|
||||
A ShellService plugin should not "daemonize" itself or othewise fork itself to the background. The I2P router will manage it as a "Client Application" and daemonizing it will break this. If your process forks itself to the background by default, it must have this feature disabled to work as a ShellService.
|
||||
|
||||
ShellService adds additional "arguments" to the applications in question,
|
||||
which are used to configure the ShellService. In order to function correctly,
|
||||
the ShellService must be named so that the name of the Plugin, i.e. `name` in
|
||||
plugin.config, must match the `-shellservice.name` argument added by the
|
||||
ShellService class. If it does not, the ShellService will be unable to look up
|
||||
the plugin process. If the final elements of the ShellService name are `-$OS-$ARCH`
|
||||
or `-$OS` they may be optionally omitted.
|
||||
ShellService adds additional "arguments" to the applications in question, which are used to configure the ShellService. In order to function correctly, the ShellService must be named so that the name of the Plugin, i.e. name in plugin.config, must match the -shellservice.name argument added by the ShellService class. If it does not, the ShellService will be unable to look up the plugin process. If the final elements of the ShellService name are -$OS-$ARCH or -$OS they may be optionally omitted.
|
||||
There are some examples in the Makefile for now.
|
||||
|
||||
Here's a copy of the usage while I work on a better README.md:
|
||||
|
||||
```markdown
|
||||
Usage of i2p.plugin.native:
|
||||
@ -50,6 +40,8 @@ Usage of i2p.plugin.native:
|
||||
Path to icon for console, which i2p.plugin.native will automatically encode
|
||||
-installonly
|
||||
Only allow installing with this plugin, fail if a previous installation exists
|
||||
-javashellservice string
|
||||
specify ShellService java path (default "net.i2p.router.web.ShellService")
|
||||
-key string
|
||||
Key to use(omit for su3)
|
||||
-license string
|
||||
@ -80,6 +72,8 @@ Usage of i2p.plugin.native:
|
||||
Require a router restart after installing or updating the plugin
|
||||
-signer string
|
||||
Signer of the plugin
|
||||
-signer-dir string
|
||||
Directory to look for signing keys
|
||||
-stopcommand string
|
||||
Command to stop client, defaults to killall exename
|
||||
-targetos string
|
||||
|
@ -1,23 +1,31 @@
|
||||
package main
|
||||
package shellservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
ClientName *string
|
||||
ClientDisplayName *string
|
||||
Command *string
|
||||
CommandArgs *string
|
||||
StopCommand *string
|
||||
Delay *string
|
||||
Start *bool
|
||||
NoShellService *bool
|
||||
CommandInPath *bool
|
||||
ExtendClassPath string
|
||||
JavaShellService *string
|
||||
ClientName *string
|
||||
ClientDisplayName *string
|
||||
Command *string
|
||||
CommandArgs *string
|
||||
StopCommand *string
|
||||
Delay *string
|
||||
Start *bool
|
||||
NoShellService *bool
|
||||
CommandInPath *bool
|
||||
Executable *string
|
||||
ExtendClassPath string
|
||||
JavaShellService *string
|
||||
NoAutoSuffixWindows *bool
|
||||
TargetOS *string
|
||||
ResourceDir *string
|
||||
}
|
||||
|
||||
func karenConfig() string {
|
||||
@ -76,7 +84,7 @@ func (cc *ClientConfig) PrintCommand() string {
|
||||
CIP = "$PLUGIN/lib/"
|
||||
}
|
||||
exesuffix := ""
|
||||
if *targetos == "windows" && !*noautosuffixwindows {
|
||||
if *cc.TargetOS == "windows" && !*cc.NoAutoSuffixWindows {
|
||||
exesuffix = ".exe"
|
||||
}
|
||||
if cc.Command == nil || *cc.Command == "" {
|
||||
@ -109,3 +117,73 @@ func (cc *ClientConfig) PrintStart() string {
|
||||
}
|
||||
return fmt.Sprintf("clientApp.0.startOnLoad=%t\n", *cc.Start)
|
||||
}
|
||||
|
||||
func (cc *ClientConfig) CopyResDir() error {
|
||||
// TODO: move the copy resdir to a function
|
||||
// in client-config.go
|
||||
if cc.ResourceDir != nil && *cc.ResourceDir != "" {
|
||||
files := find(filepath.Join(*cc.ResourceDir, "lib"), ".jar")
|
||||
for i, file := range files {
|
||||
cleaned := strings.Replace(file, *cc.ResourceDir, "$PLUGIN/", 1)
|
||||
cc.ExtendClassPath += cleaned
|
||||
fmt.Printf("%d:%d-%s\n", i, len(files), cleaned)
|
||||
if i != len(files)-1 {
|
||||
cc.ExtendClassPath += ","
|
||||
}
|
||||
}
|
||||
if err := Copy(*cc.ResourceDir, "plugin/"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ClientConfig) CopyExecutable() error {
|
||||
exesuffix := ""
|
||||
if *cc.TargetOS == "windows" && !*cc.NoAutoSuffixWindows {
|
||||
if !strings.HasSuffix(*cc.Executable, ".exe") {
|
||||
exesuffix = ".exe"
|
||||
}
|
||||
}
|
||||
if err := Copy(*cc.Executable, "plugin/lib/"+*cc.Executable+exesuffix); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod("plugin/lib/"+*cc.Executable+exesuffix, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func find(root, ext string) []string {
|
||||
var a []string
|
||||
filepath.WalkDir(root, func(s string, d fs.DirEntry, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if filepath.Ext(d.Name()) == ext {
|
||||
a = append(a, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func Copy(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
@ -2,24 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
// "archive/zip"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
// "runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/fuxingZhang/zip"
|
||||
"github.com/otiai10/copy"
|
||||
. "i2pgit.org/idk/i2p.plugin.native"
|
||||
|
||||
"i2pgit.org/idk/reseed-tools/su3"
|
||||
)
|
||||
@ -27,24 +21,11 @@ import (
|
||||
var pc PluginConfig
|
||||
var cc ClientConfig
|
||||
|
||||
var executable string
|
||||
var resdir *string
|
||||
var targetos *string
|
||||
var noautosuffixwindows *bool
|
||||
//var executable string
|
||||
//var resdir *string
|
||||
|
||||
func find(root, ext string) []string {
|
||||
var a []string
|
||||
filepath.WalkDir(root, func(s string, d fs.DirEntry, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if filepath.Ext(d.Name()) == ext {
|
||||
a = append(a, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return a
|
||||
}
|
||||
//var targetos *string
|
||||
//var noautosuffixwindows *bool
|
||||
|
||||
var javaShellService = "net.i2p.router.web.ShellService"
|
||||
|
||||
@ -52,6 +33,7 @@ func flagsSet() {
|
||||
pc.PluginName = flag.String("name", "", "Name of the plugin")
|
||||
pc.KeyName = flag.String("key", "", "Key to use(omit for su3)")
|
||||
pc.Signer = flag.String("signer", "", "Signer of the plugin")
|
||||
pc.SignerDirectory = flag.String("signer-dir", "", "Directory to look for signing keys")
|
||||
pc.Version = flag.String("version", "", "Version of the plugin")
|
||||
pc.License = flag.String("license", "", "License of the plugin")
|
||||
pc.Date = flag.String("date", "", "Release Date")
|
||||
@ -83,35 +65,15 @@ func flagsSet() {
|
||||
cc.Start = flag.Bool("autostart", true, "Start client automatically")
|
||||
//cc.NoShellService = flag.Bool("noshellservice", false, "Use ShellCommand+Karen instead of ShellService to generate plugin")
|
||||
cc.CommandInPath = flag.Bool("pathcommand", false, "Wrap a command found in the system $PATH, don't prefix the command with $PLUGIN/lib/")
|
||||
executable = *flag.String("exename", "", "Name of the executable the plugin will run, defaults to name")
|
||||
resdir = flag.String("res", "", "a directory of additional resources to include in the plugin")
|
||||
targetos = flag.String("targetos", os.Getenv("GOOS"), "Target to run the plugin on")
|
||||
noautosuffixwindows = flag.Bool("noautosuffixwindows", false, "Don't automatically add .exe to exename on Windows")
|
||||
cc.Executable = flag.String("exename", "", "Name of the executable the plugin will run, defaults to name")
|
||||
cc.ResourceDir = flag.String("res", "", "a directory of additional resources to include in the plugin")
|
||||
cc.TargetOS = flag.String("targetos", os.Getenv("GOOS"), "Target to run the plugin on")
|
||||
cc.NoAutoSuffixWindows = flag.Bool("noautosuffixwindows", false, "Don't automatically add .exe to exename on Windows")
|
||||
cc.JavaShellService = flag.String("javashellservice", javaShellService, "specify ShellService java path")
|
||||
flag.Parse()
|
||||
cc.ClientDisplayName = pc.ConsoleLinkName
|
||||
}
|
||||
|
||||
func Copy(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func goBin() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" {
|
||||
@ -123,37 +85,25 @@ func goBin() string {
|
||||
func main() {
|
||||
flagsSet()
|
||||
|
||||
if executable != "" {
|
||||
cc.ClientName = &executable
|
||||
if *cc.Executable != "" {
|
||||
*cc.ClientName = *cc.Executable
|
||||
}
|
||||
|
||||
cc.CheckClientName(*pc.PluginName)
|
||||
|
||||
if executable == "" {
|
||||
executable = *cc.ClientName
|
||||
if *cc.Executable == "" {
|
||||
*cc.Executable = *cc.ClientName
|
||||
}
|
||||
|
||||
fmt.Printf("executable:%s\n", executable)
|
||||
fmt.Printf("resources:%s\n", *resdir)
|
||||
fmt.Printf("executable:%s\n", *cc.Executable)
|
||||
fmt.Printf("resources:%s\n", *cc.ResourceDir)
|
||||
|
||||
os.RemoveAll("plugin")
|
||||
if err := os.MkdirAll("plugin/lib", 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if resdir != nil && *resdir != "" {
|
||||
files := find(filepath.Join(*resdir, "lib"), ".jar")
|
||||
for i, file := range files {
|
||||
cleaned := strings.Replace(file, *resdir, "$PLUGIN/", 1)
|
||||
cc.ExtendClassPath += cleaned
|
||||
fmt.Printf("%d:%d-%s\n", i, len(files), cleaned)
|
||||
if i != len(files)-1 {
|
||||
cc.ExtendClassPath += ","
|
||||
}
|
||||
}
|
||||
if err := copy.Copy(*resdir, "plugin/"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := cc.CopyResDir(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("plugin.config:\n\t%s\n", pc.Print())
|
||||
@ -166,17 +116,8 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
exesuffix := ""
|
||||
if *targetos == "windows" && !*noautosuffixwindows {
|
||||
if !strings.HasSuffix(executable, ".exe") {
|
||||
exesuffix = ".exe"
|
||||
}
|
||||
}
|
||||
|
||||
if err := Copy(executable, "plugin/lib/"+executable+exesuffix); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod("plugin/lib/"+executable+exesuffix, 0755); err != nil {
|
||||
// TODO: move exe-copy logic into plugin-config.go
|
||||
if err := cc.CopyExecutable(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@ -195,49 +136,9 @@ func main() {
|
||||
}
|
||||
|
||||
func createZip() error {
|
||||
err := zip.Dir("plugin", *pc.PluginName+".zip", false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return err
|
||||
return pc.CreateZip()
|
||||
}
|
||||
|
||||
func createSu3() (*su3.File, error) {
|
||||
su3File := su3.New()
|
||||
su3File.FileType = su3.FileTypeZIP
|
||||
su3File.ContentType = su3.ContentTypePlugin
|
||||
su3File.Version = []byte(*pc.Version)
|
||||
|
||||
err := createZip()
|
||||
if err != err {
|
||||
return nil, err
|
||||
}
|
||||
zipped, err := ioutil.ReadFile(*pc.PluginName + ".zip")
|
||||
if err != err {
|
||||
return nil, err
|
||||
}
|
||||
su3File.Content = zipped
|
||||
|
||||
su3File.SignerID = []byte(*pc.Signer)
|
||||
sk, err := loadPrivateKey(*pc.Signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
su3File.Sign(sk)
|
||||
return su3File, nil
|
||||
}
|
||||
|
||||
func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
||||
privPem, err := ioutil.ReadFile(strings.Replace(path, "@", "_at_", -1) + ".pem")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privDer, _ := pem.Decode(privPem)
|
||||
privKey, err := x509.ParsePKCS1PrivateKey(privDer.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
return pc.CreateSu3()
|
||||
}
|
||||
|
9
desc
9
desc
@ -1 +1,8 @@
|
||||
Railroad is a user-friendly anonymous blogging tool.
|
||||
ShellService Plugin Generator
|
||||
=============================
|
||||
|
||||
Generates a valid ShellService plugin from a single script or executable. A ShellService plugin is an application which runs as a process which is managed by the I2P sofware. This allows I2P to manage the lifetime of a non-JVM application by monitoring it. That way, plugins don't risk outliving the I2P router because the router loses track of them.
|
||||
|
||||
A ShellService plugin should not "daemonize" itself or othewise fork itself to the background. The I2P router will manage it as a "Client Application" and daemonizing it will break this. If your process forks itself to the background by default, it must have this feature disabled to work as a ShellService.
|
||||
|
||||
ShellService adds additional "arguments" to the applications in question, which are used to configure the ShellService. In order to function correctly, the ShellService must be named so that the name of the Plugin, i.e. name in plugin.config, must match the -shellservice.name argument added by the ShellService class. If it does not, the ShellService will be unable to look up the plugin process. If the final elements of the ShellService name are -$OS-$ARCH or -$OS they may be optionally omitted.
|
@ -1,11 +1,18 @@
|
||||
package main
|
||||
package shellservice
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fuxingZhang/zip"
|
||||
"i2pgit.org/idk/reseed-tools/su3"
|
||||
)
|
||||
|
||||
type PluginConfig struct {
|
||||
@ -37,7 +44,7 @@ type PluginConfig struct {
|
||||
OnlyInstall *bool //25
|
||||
ConsoleLinkTip *string //26
|
||||
ConsoleLinkTipLang []*string //27
|
||||
|
||||
SignerDirectory *string //28
|
||||
}
|
||||
|
||||
func (pc *PluginConfig) Print() string {
|
||||
@ -233,3 +240,59 @@ func (pc *PluginConfig) PrintMinJetty() string {
|
||||
func (pc *PluginConfig) PrintMaxJetty() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (pc *PluginConfig) CreateZip() error {
|
||||
err := zip.Dir("plugin", *pc.PluginName+".zip", false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pc *PluginConfig) CreateSu3() (*su3.File, error) {
|
||||
su3File := su3.New()
|
||||
su3File.FileType = su3.FileTypeZIP
|
||||
su3File.ContentType = su3.ContentTypePlugin
|
||||
su3File.Version = []byte(*pc.Version)
|
||||
|
||||
err := pc.CreateZip()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zipped, err := ioutil.ReadFile(*pc.PluginName + ".zip")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
su3File.Content = zipped
|
||||
|
||||
su3File.SignerID = []byte(*pc.Signer)
|
||||
sk, err := pc.LoadPrivateKey(*pc.Signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
su3File.Sign(sk)
|
||||
return su3File, nil
|
||||
}
|
||||
|
||||
func (pc *PluginConfig) LoadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
||||
keys, err := pc.keysPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privPem, err := ioutil.ReadFile(keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privDer, _ := pem.Decode(privPem)
|
||||
privKey, err := x509.ParsePKCS1PrivateKey(privDer.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
func (pc *PluginConfig) keysPath(path string) (string, error) {
|
||||
return filepath.Abs(filepath.Join(*pc.SignerDirectory, strings.Replace(path, "@", "_at_", -1)+".pem"))
|
||||
}
|
Reference in New Issue
Block a user