rewrite to be more dnf-like, with config editing
This commit is contained in:
parent
81adb51f5b
commit
99f4220a67
@ -1,6 +1,6 @@
|
|||||||
# A tool to manage plugins on play.malloc.gg
|
# Minecraft Plugin Sync
|
||||||
|
|
||||||
plugin-sync is a tool used to maintain the various servers and their plugins on
|
mpm is a tool used to maintain the various servers and their plugins on
|
||||||
play.malloc.gg.
|
play.malloc.gg.
|
||||||
|
|
||||||
Malloc runs a heterogenous environment of server versions, depending on what
|
Malloc runs a heterogenous environment of server versions, depending on what
|
||||||
@ -12,7 +12,7 @@ tool to help out the workload.
|
|||||||
|
|
||||||
## Use cases
|
## Use cases
|
||||||
|
|
||||||
plugin-sync is highly specialized for the purposes of malloc.gg, but it should
|
mpm is highly specialized for the purposes of malloc.gg, but it should
|
||||||
be able to work in other systems where:
|
be able to work in other systems where:
|
||||||
|
|
||||||
- You have multiple servers in a network
|
- You have multiple servers in a network
|
||||||
|
267
main.py
Executable file
267
main.py
Executable file
@ -0,0 +1,267 @@
|
|||||||
|
#!/bin/env python
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
|
||||||
|
from repo import Repo
|
||||||
|
from server import Server
|
||||||
|
from model import *
|
||||||
|
|
||||||
|
try:
|
||||||
|
from yaml import CLoader as Loader, CDumper as Dumper
|
||||||
|
except ImportError:
|
||||||
|
from yaml import Loader, Dumper
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = """
|
||||||
|
repositories: {}
|
||||||
|
servers: {}
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Config():
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
if path is None:
|
||||||
|
path = './mpm.yaml'
|
||||||
|
self.path = path
|
||||||
|
with open(path, 'r') as fd:
|
||||||
|
self.yaml = yaml.load(fd, Loader=Loader)
|
||||||
|
self.config = yaml.load(DEFAULT_CONFIG, Loader=Loader)
|
||||||
|
if isinstance(self.yaml, dict):
|
||||||
|
self.config.update(self.yaml)
|
||||||
|
|
||||||
|
def repository(self, name):
|
||||||
|
return Repo(name, self.config['repositories'][name])
|
||||||
|
|
||||||
|
def repositories(self):
|
||||||
|
return [Repo(name, c) for (name, c) in self.config['repositories'].items()]
|
||||||
|
|
||||||
|
def update_repository(self, name, config):
|
||||||
|
self.config['repositories'][name] = config
|
||||||
|
|
||||||
|
def add_repository(self, name, path):
|
||||||
|
if name in self.config['repositories']:
|
||||||
|
raise ValueError('Repository already exists')
|
||||||
|
|
||||||
|
self.update_repository(name, {
|
||||||
|
'path': path
|
||||||
|
})
|
||||||
|
|
||||||
|
def servers(self):
|
||||||
|
return [Server(name, c) for (name, c) in self.config['servers'].items()]
|
||||||
|
|
||||||
|
def server(self, name):
|
||||||
|
return Server(name, self.config['servers'][name])
|
||||||
|
|
||||||
|
def update_server(self, server, config):
|
||||||
|
self.config['servers'][server] = config
|
||||||
|
|
||||||
|
def add_server(self, name, path):
|
||||||
|
if name in self.config['servers']:
|
||||||
|
raise ValueError("Server already exists")
|
||||||
|
|
||||||
|
self.update_server(name, {
|
||||||
|
'path': path,
|
||||||
|
'plugins': [],
|
||||||
|
'inherit': []
|
||||||
|
})
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
stream = open(self.path, 'w')
|
||||||
|
yaml.dump(self.config, stream, Dumper=Dumper)
|
||||||
|
stream.close()
|
||||||
|
|
||||||
|
def do_repo_add(args, config):
|
||||||
|
if not os.path.exists(args.path):
|
||||||
|
os.makedirs(args.path)
|
||||||
|
config.add_repository(args.name, args.path)
|
||||||
|
config.save()
|
||||||
|
print("Added repository {}".format(args.path))
|
||||||
|
|
||||||
|
def do_repo_list(args, config):
|
||||||
|
for repo in config.repositories():
|
||||||
|
print("{} ({})".format(repo.name, repo.path))
|
||||||
|
for plugin in sorted(repo.plugins()):
|
||||||
|
print('\t', plugin.name, '\t', plugin.version)
|
||||||
|
for badFile in sorted(repo.badFiles()):
|
||||||
|
print('\tWARNING: Unknown file', badFile)
|
||||||
|
|
||||||
|
def do_repo_import(args, config):
|
||||||
|
repo = config.repository(args.name)
|
||||||
|
plugins = []
|
||||||
|
for path in args.path:
|
||||||
|
try:
|
||||||
|
plugins.append(Plugin(path))
|
||||||
|
except:
|
||||||
|
print("Bad plugin filename {}".format(path))
|
||||||
|
|
||||||
|
if len(plugins) == 0:
|
||||||
|
print("No plugins found.")
|
||||||
|
|
||||||
|
print('Found the following plugins:')
|
||||||
|
for plugin in plugins:
|
||||||
|
print("\t{} {}".format(plugin.name, plugin.version))
|
||||||
|
print("Import plugins into {}? [y/N]".format(repo.name))
|
||||||
|
answer = input().lower()
|
||||||
|
if answer == "y":
|
||||||
|
for plugin in plugins:
|
||||||
|
repo.importPlugin(plugin)
|
||||||
|
print("Imported!")
|
||||||
|
else:
|
||||||
|
print("Cancelled.")
|
||||||
|
|
||||||
|
def do_server_add(args, config):
|
||||||
|
config.add_server(args.name, args.path)
|
||||||
|
config.save()
|
||||||
|
print("Added server {} in {}".format(args.name, args.path))
|
||||||
|
|
||||||
|
def do_server_list(args, config):
|
||||||
|
for server in config.servers():
|
||||||
|
print('{} ({}):'.format(server.name, server.path))
|
||||||
|
outdatedLinks = []
|
||||||
|
missing = []
|
||||||
|
installed = []
|
||||||
|
unmanaged = []
|
||||||
|
conflicts = []
|
||||||
|
for state in sorted(server.pluginStates(config.repositories())):
|
||||||
|
if isinstance(state, OutdatedSymlink):
|
||||||
|
outdatedLinks.append(state)
|
||||||
|
elif isinstance(state, Installed):
|
||||||
|
installed.append(state)
|
||||||
|
elif isinstance(state, MissingVersions):
|
||||||
|
missing.append(state)
|
||||||
|
elif isinstance(state, UnmanagedFile):
|
||||||
|
unmanaged.append(state)
|
||||||
|
elif isinstance(state, SymlinkConflict):
|
||||||
|
conflicts.append(state)
|
||||||
|
|
||||||
|
print("Installed plugins:")
|
||||||
|
for state in sorted(installed):
|
||||||
|
print("\t{} {}: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion))
|
||||||
|
print("Oudated symlinks:")
|
||||||
|
for state in sorted(outdatedLinks):
|
||||||
|
print("\t{} {}: Current: {} Wanted: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion, state.wantedVersion))
|
||||||
|
print("Missing plugins:")
|
||||||
|
for state in sorted(missing):
|
||||||
|
print("\t{}: {}".format(state.plugin.name, state.plugin.versionSpec))
|
||||||
|
print("Unmanaged files:")
|
||||||
|
for state in sorted(unmanaged):
|
||||||
|
print("\t{}".format(state.filename))
|
||||||
|
print("Symlink Conflicts:")
|
||||||
|
for state in sorted(conflicts):
|
||||||
|
print("\t{}.jar".format(state.plugin.name))
|
||||||
|
|
||||||
|
def do_server_add_plugin(args, config):
|
||||||
|
server = config.server(args.server)
|
||||||
|
plugins = []
|
||||||
|
for pluginSpec in args.plugin:
|
||||||
|
|
||||||
|
if os.path.exists(pluginSpec):
|
||||||
|
plugin = Plugin(pluginSpec)
|
||||||
|
pluginSpec = PluginSpec(plugin.name, str(plugin.version))
|
||||||
|
else:
|
||||||
|
allVersions = []
|
||||||
|
for repo in config.repositories():
|
||||||
|
allVersions += repo.versionsForPlugin(pluginSpec)
|
||||||
|
pluginSpec = PluginSpec(pluginSpec, list(reversed(sorted(allVersions)))[0])
|
||||||
|
|
||||||
|
plugins.append(pluginSpec)
|
||||||
|
|
||||||
|
print("Added {} to {}".format(pluginSpec, server.name))
|
||||||
|
for pluginSpec in plugins:
|
||||||
|
print("\t{} {}".format(pluginSpec.name, pluginSpec.versionSpec))
|
||||||
|
print("Add these plugins to server {}? [y/N]".format(server.name))
|
||||||
|
answer = input().lower()
|
||||||
|
if answer == "y":
|
||||||
|
for pluginSpec in plugins:
|
||||||
|
server.add_plugin(pluginSpec)
|
||||||
|
config.update_server(server.name, server.config)
|
||||||
|
config.save()
|
||||||
|
print("Added!")
|
||||||
|
else:
|
||||||
|
print("Cancelled.")
|
||||||
|
|
||||||
|
def do_server_sync(args, config):
|
||||||
|
for server in config.servers():
|
||||||
|
print('{} ({}):'.format(server.name, server.path))
|
||||||
|
outdatedLinks = []
|
||||||
|
available = []
|
||||||
|
for state in sorted(server.pluginStates(config.repositories())):
|
||||||
|
if isinstance(state, OutdatedSymlink):
|
||||||
|
outdatedLinks.append(state)
|
||||||
|
elif isinstance(state, Available):
|
||||||
|
available.append(state)
|
||||||
|
|
||||||
|
print("Plugins to update:")
|
||||||
|
for state in sorted(outdatedLinks):
|
||||||
|
print("\t{} {}: Current: {} Wanted: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion, state.wantedVersion))
|
||||||
|
print("New plugins to install:")
|
||||||
|
for state in sorted(available):
|
||||||
|
print("\t{}: {}".format(state.plugin.name, state.plugin.version))
|
||||||
|
|
||||||
|
if len(outdatedLinks) > 0 or len(available) > 0:
|
||||||
|
print("Apply changes? [y/N]")
|
||||||
|
answer = input().lower()
|
||||||
|
if answer == "y":
|
||||||
|
for state in available:
|
||||||
|
server.installVersion(state.plugin)
|
||||||
|
server.updateSymlinkForPlugin(state.plugin, state.plugin.version)
|
||||||
|
print("Installed {} {}".format(state.plugin.name, state.plugin.version))
|
||||||
|
for state in outdatedLinks:
|
||||||
|
server.updateSymlinkForPlugin(state.plugin, state.wantedVersion)
|
||||||
|
print("Updated {} to {}".format(state.plugin.name, state.wantedVersion))
|
||||||
|
else:
|
||||||
|
print("Not applying changes.")
|
||||||
|
else:
|
||||||
|
print("No changes to apply.")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Paper Plugin Sync')
|
||||||
|
parser.add_argument('--config', dest='config_path', type=Config)
|
||||||
|
subparsers = parser.add_subparsers()
|
||||||
|
repos = subparsers.add_parser('repo')
|
||||||
|
repo_sub = repos.add_subparsers()
|
||||||
|
|
||||||
|
repo_add = repo_sub.add_parser('add')
|
||||||
|
repo_add.add_argument('name', help='Name of the repository')
|
||||||
|
repo_add.add_argument('path', help='Where to add a repository or create a new one')
|
||||||
|
repo_add.set_defaults(func=do_repo_add)
|
||||||
|
|
||||||
|
repo_list = repo_sub.add_parser('list')
|
||||||
|
repo_list.set_defaults(func=do_repo_list)
|
||||||
|
|
||||||
|
repo_import = repo_sub.add_parser('import')
|
||||||
|
repo_import.add_argument('name', help='Name of the repository')
|
||||||
|
repo_import.add_argument('path', nargs="+", help='Path of the plugin to import')
|
||||||
|
repo_import.set_defaults(func=do_repo_import)
|
||||||
|
|
||||||
|
servers = subparsers.add_parser('server')
|
||||||
|
server_sub = servers.add_subparsers()
|
||||||
|
server_add = server_sub.add_parser('add')
|
||||||
|
server_add.add_argument('name', help='Name for the server')
|
||||||
|
server_add.add_argument('path', help='Path to your server\'s root directory')
|
||||||
|
server_add.set_defaults(func=do_server_add)
|
||||||
|
|
||||||
|
server_list = server_sub.add_parser('list')
|
||||||
|
server_list.set_defaults(func=do_server_list)
|
||||||
|
|
||||||
|
server_add_plugin = server_sub.add_parser('add-plugin')
|
||||||
|
server_add_plugin.add_argument('server', help='Name of server to modify')
|
||||||
|
server_add_plugin.add_argument('plugin', nargs='+', help='Plugin file or spec to install')
|
||||||
|
server_add_plugin.set_defaults(func=do_server_add_plugin)
|
||||||
|
|
||||||
|
server_sync = server_sub.add_parser('sync')
|
||||||
|
server_sync.set_defaults(func=do_server_sync)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config = Config(args.config_path)
|
||||||
|
|
||||||
|
if 'func' not in args:
|
||||||
|
parser.print_usage()
|
||||||
|
else:
|
||||||
|
args.func(args, config)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
99
model.py
Normal file
99
model.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
from semantic_version import Spec, Version
|
||||||
|
from functools import total_ordering
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
#version_pattern = re.compile('^(?P<name>.*)-(?P<version>[^-]+)(?P<extra>-[^-]+)?\.jar$')
|
||||||
|
version_pattern = re.compile('^(?P<name>.*)-(?P<version>[^-]+(?:-[^-]+)?)\.jar$')
|
||||||
|
version_pattern = re.compile('^(?P<name>.+)-(?P<version>(?:\.?\d+)+).+jar$')
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class Plugin:
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
pluginName = os.path.basename(path)
|
||||||
|
pluginMatches = version_pattern.match(pluginName)
|
||||||
|
|
||||||
|
if pluginMatches is None:
|
||||||
|
raise ValueError("Cannot derive plugin name from '{}'".format(path))
|
||||||
|
|
||||||
|
self.name = pluginMatches['name']
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.version = Version.coerce(pluginMatches['version'])
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("Cannot derive semver from '{}'".format(path))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.name == other.name and self.version == other.version
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.name != other.name or self.version != other.version
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if self.name == other.name:
|
||||||
|
return self.version < other.version
|
||||||
|
return self.name < other.name
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class PluginSpec:
|
||||||
|
def __init__(self, name, versionSpec):
|
||||||
|
self.name = name
|
||||||
|
try:
|
||||||
|
self.versionSpec = Spec(str(versionSpec))
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("Invalid version spec for plugin {}: {}".format(name, versionSpec))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} {}".format(self.name, self.versionSpec)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.name == other.name and self.versionSpec == other.versionSpec
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.name != other.name or self.versionSpec != other.versionSpec
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.name < other.name
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class PluginState:
|
||||||
|
def __init__(self, plugin):
|
||||||
|
self.plugin = plugin
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.plugin == other.plugin
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.plugin != other.plugin
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.plugin.name < other.plugin.name
|
||||||
|
|
||||||
|
class UnmanagedFile(PluginState):
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.filename < other.filename
|
||||||
|
|
||||||
|
class OutdatedSymlink(PluginState):
|
||||||
|
def __init__(self, plugin, currentVersion, wantedVersion):
|
||||||
|
super().__init__(plugin)
|
||||||
|
self.currentVersion = currentVersion
|
||||||
|
self.wantedVersion = wantedVersion
|
||||||
|
|
||||||
|
class SymlinkConflict(PluginState):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MissingVersions(PluginState):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Available(PluginState):
|
||||||
|
def __init__(self, repoPlugin):
|
||||||
|
super().__init__(repoPlugin)
|
||||||
|
|
||||||
|
class Installed(PluginState):
|
||||||
|
def __init__(self, plugin, currentVersion):
|
||||||
|
super().__init__(plugin)
|
||||||
|
self.currentVersion = currentVersion
|
191
plugin-sync.py
191
plugin-sync.py
@ -1,191 +0,0 @@
|
|||||||
#!/bin/env python
|
|
||||||
import yaml
|
|
||||||
from semantic_version import Spec, Version
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from functools import total_ordering
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CLoader as Loader, CDumper as Dumper
|
|
||||||
except ImportError:
|
|
||||||
from yaml import Loader, Dumper
|
|
||||||
|
|
||||||
conf = yaml.load(open('plugins.yml', 'r'), Loader=Loader)
|
|
||||||
|
|
||||||
class PluginSpec:
|
|
||||||
def __init__(self, name, versionSpec):
|
|
||||||
self.name = name
|
|
||||||
try:
|
|
||||||
self.versionSpec = Spec(versionSpec)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("Invalid version spec for plugin {}: {}".format(name, versionSpec))
|
|
||||||
|
|
||||||
@total_ordering
|
|
||||||
class PluginState:
|
|
||||||
def __init__(self, plugin):
|
|
||||||
self.plugin = plugin
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.plugin == other.plugin
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return self.plugin != other.plugin
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
return self.plugin.name < other.plugin.name
|
|
||||||
|
|
||||||
class UnmanagedFile(PluginState):
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.filename = filename
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
return self.filename < other.filename
|
|
||||||
|
|
||||||
class OutdatedSymlink(PluginState):
|
|
||||||
def __init__(self, plugin, currentVersion, wantedVersion):
|
|
||||||
super().__init__(plugin)
|
|
||||||
self.currentVersion = currentVersion
|
|
||||||
self.wantedVersion = wantedVersion
|
|
||||||
|
|
||||||
class SymlinkConflict(PluginState):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MissingVersions(PluginState):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Installed(PluginState):
|
|
||||||
def __init__(self, plugin, currentVersion):
|
|
||||||
super().__init__(plugin)
|
|
||||||
self.currentVersion = currentVersion
|
|
||||||
|
|
||||||
class Repo:
|
|
||||||
def __init__(self, path, pluginSet):
|
|
||||||
self.path = path
|
|
||||||
self.pluginSet = pluginSet
|
|
||||||
|
|
||||||
def updateSymlinkForPlugin(self, plugin, version):
|
|
||||||
pluginFilename = os.path.join(self.path, 'versions/{}-{}.jar'.format(plugin.name, version))
|
|
||||||
pluginSymlink = os.path.join(self.path, plugin.name + '.jar')
|
|
||||||
linkDst = os.path.relpath(pluginFilename, self.path)
|
|
||||||
|
|
||||||
if os.path.lexists(pluginSymlink):
|
|
||||||
os.unlink(pluginSymlink)
|
|
||||||
os.symlink(linkDst, pluginSymlink)
|
|
||||||
|
|
||||||
def pluginStates(self):
|
|
||||||
managedPluginFilenames = []
|
|
||||||
for plugin in self.pluginSet:
|
|
||||||
compatibleVersions = []
|
|
||||||
pluginLinkName = '{}.jar'.format(plugin.name)
|
|
||||||
managedPluginFilenames.append(pluginLinkName)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(self.path, pluginLinkName)) and not os.path.islink(os.path.join(self.path, pluginLinkName)):
|
|
||||||
yield SymlinkConflict(plugin)
|
|
||||||
continue
|
|
||||||
|
|
||||||
for installedVersion in self.versionsForPlugin(plugin.name):
|
|
||||||
if installedVersion in plugin.versionSpec:
|
|
||||||
compatibleVersions.append(installedVersion)
|
|
||||||
|
|
||||||
if len(compatibleVersions) == 0:
|
|
||||||
yield MissingVersions(plugin)
|
|
||||||
else:
|
|
||||||
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
|
||||||
currentVersion = self.currentVersionForPlugin(plugin.name)
|
|
||||||
|
|
||||||
if currentVersion == preferredVersion:
|
|
||||||
yield Installed(plugin, currentVersion)
|
|
||||||
else:
|
|
||||||
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
|
|
||||||
|
|
||||||
otherPlugins = os.listdir(self.path)
|
|
||||||
for pluginFile in otherPlugins:
|
|
||||||
if os.path.isfile(os.path.join(self.path, pluginFile)) and pluginFile not in managedPluginFilenames:
|
|
||||||
yield UnmanagedFile(pluginFile)
|
|
||||||
|
|
||||||
def currentVersionForPlugin(self, pluginName):
|
|
||||||
pluginSymlink = os.path.join(self.path, pluginName + '.jar')
|
|
||||||
if not os.path.lexists(pluginSymlink):
|
|
||||||
return None
|
|
||||||
suffix = '.jar'
|
|
||||||
pluginJar = os.path.basename(os.readlink(pluginSymlink))
|
|
||||||
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
|
|
||||||
try:
|
|
||||||
pluginSemver = Version.coerce(jarVersion)
|
|
||||||
except ValueError:
|
|
||||||
pluginSemver = jarVersion
|
|
||||||
return pluginSemver
|
|
||||||
|
|
||||||
def versionsForPlugin(self, pluginName):
|
|
||||||
plugins = os.listdir(os.path.join(self.path, 'versions'))
|
|
||||||
for pluginJar in plugins:
|
|
||||||
if pluginJar.startswith(pluginName):
|
|
||||||
prefix = pluginName + '-'
|
|
||||||
suffix = '.jar'
|
|
||||||
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
|
|
||||||
try:
|
|
||||||
pluginSemver = Version.coerce(jarVersion)
|
|
||||||
except ValueError:
|
|
||||||
pluginSemver = jarVersion
|
|
||||||
yield pluginSemver
|
|
||||||
|
|
||||||
for (serverName,server) in conf['servers'].items():
|
|
||||||
changeset = []
|
|
||||||
if len(sys.argv) > 1 and serverName not in sys.argv[1:]:
|
|
||||||
continue
|
|
||||||
if 'pluginPath' not in server:
|
|
||||||
continue
|
|
||||||
if not os.path.exists(server['pluginPath']):
|
|
||||||
print("Missing plugin path for {}: {}".format(serverName, server['pluginPath']))
|
|
||||||
else:
|
|
||||||
print("=== Updating server {}".format(serverName))
|
|
||||||
pluginSpecs = {}
|
|
||||||
for inherited in server.get('inherit', ()):
|
|
||||||
for inheritedPlugin in conf['servers'][inherited]['plugins']:
|
|
||||||
pluginSpecs[inheritedPlugin['name']] = PluginSpec(inheritedPlugin['name'], str(inheritedPlugin.get('version', '*')))
|
|
||||||
|
|
||||||
for pluginConf in server.get('plugins', ()):
|
|
||||||
pluginSpecs[pluginConf['name']] = PluginSpec(pluginConf['name'], str(pluginConf.get('version', '*')))
|
|
||||||
|
|
||||||
repo = Repo(server['pluginPath'], pluginSpecs.values())
|
|
||||||
outdatedLinks = []
|
|
||||||
missing = []
|
|
||||||
installed = []
|
|
||||||
unmanaged = []
|
|
||||||
conflicts = []
|
|
||||||
for state in repo.pluginStates():
|
|
||||||
if isinstance(state, OutdatedSymlink):
|
|
||||||
outdatedLinks.append(state)
|
|
||||||
elif isinstance(state, Installed):
|
|
||||||
installed.append(state)
|
|
||||||
elif isinstance(state, MissingVersions):
|
|
||||||
missing.append(state)
|
|
||||||
elif isinstance(state, UnmanagedFile):
|
|
||||||
unmanaged.append(state)
|
|
||||||
elif isinstance(state, SymlinkConflict):
|
|
||||||
conflicts.append(state)
|
|
||||||
|
|
||||||
print("Installed plugins:")
|
|
||||||
for state in sorted(installed):
|
|
||||||
print("\t{} {}: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion))
|
|
||||||
print("Oudated symlinks:")
|
|
||||||
for state in sorted(outdatedLinks):
|
|
||||||
print("\t{} {}: Current: {} Wanted: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion, state.wantedVersion))
|
|
||||||
print("Missing plugins:")
|
|
||||||
for state in sorted(missing):
|
|
||||||
print("\t{}: {}".format(state.plugin.name, state.plugin.versionSpec))
|
|
||||||
print("Unmanaged files:")
|
|
||||||
for state in sorted(unmanaged):
|
|
||||||
print("\t{}".format(state.filename))
|
|
||||||
print("Symlink Conflicts:")
|
|
||||||
for state in sorted(conflicts):
|
|
||||||
print("\t{}.jar".format(state.plugin.name))
|
|
||||||
|
|
||||||
if len(outdatedLinks) > 0:
|
|
||||||
print("Apply changes? [y/N]")
|
|
||||||
answer = input().lower()
|
|
||||||
if answer == "y":
|
|
||||||
for state in outdatedLinks:
|
|
||||||
repo.updateSymlinkForPlugin(state.plugin, state.wantedVersion)
|
|
||||||
else:
|
|
||||||
print("Not applying changes.")
|
|
183
plugins.yml
183
plugins.yml
@ -1,183 +0,0 @@
|
|||||||
servers:
|
|
||||||
1.17-smp:
|
|
||||||
plugins:
|
|
||||||
- name: Pl3xMap
|
|
||||||
- name: pl3xmap-signs
|
|
||||||
1.18-common:
|
|
||||||
plugins:
|
|
||||||
- name: voicechat-bukkit-1.18.2
|
|
||||||
version: 2.2.28
|
|
||||||
1.17-common:
|
|
||||||
plugins:
|
|
||||||
- name: voicechat-bukkit-1.17.1
|
|
||||||
version: 2.2.28
|
|
||||||
smp-common:
|
|
||||||
plugins:
|
|
||||||
- name: ActionHealth
|
|
||||||
creative:
|
|
||||||
inherit:
|
|
||||||
- common
|
|
||||||
- 1.17-common
|
|
||||||
pluginPath: /srv/minecraft/creative/plugins/
|
|
||||||
plugins:
|
|
||||||
- name: HolographicDisplays
|
|
||||||
- name: HolographicExtension
|
|
||||||
- name: EssentialsXSpawn
|
|
||||||
- name: WorldSystem
|
|
||||||
version: 2.4.12
|
|
||||||
villager-defense:
|
|
||||||
inherit:
|
|
||||||
- common
|
|
||||||
- 1.17-common
|
|
||||||
pluginPath: /srv/minecraft/villager-defense/plugins/
|
|
||||||
mcdev:
|
|
||||||
inherit:
|
|
||||||
- common
|
|
||||||
- smp-common
|
|
||||||
- 1.18-common
|
|
||||||
- SMP
|
|
||||||
pluginPath: /srv/minecraft/mcdev/plugins/
|
|
||||||
plugins:
|
|
||||||
- name: tabtps-spigot
|
|
||||||
version: '*'
|
|
||||||
- name: item-nbt-api-plugin
|
|
||||||
version: 2.9.2
|
|
||||||
- name: QuickShop
|
|
||||||
version: 5
|
|
||||||
- name: AngelChest
|
|
||||||
version: '*'
|
|
||||||
- name: GameModeInventories
|
|
||||||
version: '*'
|
|
||||||
SMP:
|
|
||||||
inherit:
|
|
||||||
- common
|
|
||||||
- smp-common
|
|
||||||
- 1.17-common
|
|
||||||
- 1.17-smp
|
|
||||||
pluginPath: /srv/minecraft/server/plugins/
|
|
||||||
plugins:
|
|
||||||
- name: ATMSigns
|
|
||||||
- name: Harbor
|
|
||||||
- name: BetterRTP
|
|
||||||
- name: CoreProtect
|
|
||||||
version: 19.5.*
|
|
||||||
- name: Chunky
|
|
||||||
version: 1.2.86
|
|
||||||
- name: ChunkyBorder
|
|
||||||
version: 1.0.38
|
|
||||||
- name: GameModeInventories
|
|
||||||
version: 3.3.3
|
|
||||||
- name: HolographicDisplays
|
|
||||||
version: 2
|
|
||||||
- name: Regions
|
|
||||||
version: '0.3'
|
|
||||||
- name: Shopkeepers
|
|
||||||
version: 2.13.2
|
|
||||||
- name: Lands
|
|
||||||
version: 5.11.18
|
|
||||||
- name: OpenInv
|
|
||||||
version: 4.1.8
|
|
||||||
- name: AngelChest
|
|
||||||
version: 4.2.0
|
|
||||||
- name: PvPManager
|
|
||||||
version: 3
|
|
||||||
- name: EssentialsXSpawn
|
|
||||||
version: 2
|
|
||||||
- name: MythicMobs
|
|
||||||
version: 5.0.0-alpha1
|
|
||||||
- name: Model-Engine
|
|
||||||
version: 2.*
|
|
||||||
- name: LibsDisguises
|
|
||||||
version: 10
|
|
||||||
- name: QuickShop
|
|
||||||
version: 4.0
|
|
||||||
- name: ProtocolLib
|
|
||||||
version: 4.8.0
|
|
||||||
skyblock:
|
|
||||||
inherit:
|
|
||||||
- 1.18-common
|
|
||||||
- common
|
|
||||||
pluginPath: /srv/minecraft/skyblock/plugins/
|
|
||||||
plugins:
|
|
||||||
- name: BentoBox
|
|
||||||
version: 1.19.0
|
|
||||||
lobby:
|
|
||||||
pluginPath: /srv/minecraft/lobby/plugins/
|
|
||||||
inherit:
|
|
||||||
- common
|
|
||||||
plugins:
|
|
||||||
- name: ItemJoin
|
|
||||||
- name: Parkour
|
|
||||||
version: 6.7.1
|
|
||||||
- name: GadgetsMenu
|
|
||||||
version: 5.0.2
|
|
||||||
- name: Denizen
|
|
||||||
version: 1.2.1-b1743-REL
|
|
||||||
- name: JumpPads
|
|
||||||
version: 1.25.6
|
|
||||||
- name: VoidSpawn
|
|
||||||
version: 1.0.1
|
|
||||||
- name: EssentialsXSpawn
|
|
||||||
version: 2.18.2
|
|
||||||
- name: HolographicExtension
|
|
||||||
version: 1.10.9
|
|
||||||
- name: HolographicDisplays
|
|
||||||
version: 2.4.9
|
|
||||||
- name: Citizens
|
|
||||||
version: 2.0.28-5
|
|
||||||
- name: voicechat-bukkit-1.17.1
|
|
||||||
version: 2.2.28
|
|
||||||
common:
|
|
||||||
plugins:
|
|
||||||
- name: Advanced-Portals
|
|
||||||
version: 0.7.1
|
|
||||||
- name: DeluxeMenus
|
|
||||||
version: 1.13.3
|
|
||||||
- name: AsyncWorldEdit
|
|
||||||
version: 3.9.1
|
|
||||||
- name: CoordinatesHUD
|
|
||||||
- name: BungeeTabListPlus_BukkitBridge
|
|
||||||
version: ~3.4
|
|
||||||
- name: BuycraftX
|
|
||||||
- name: DBVerifier
|
|
||||||
version: 1.6.5-malloc
|
|
||||||
- name: DiscordSRV
|
|
||||||
version: 1.24.0
|
|
||||||
- name: EssentialsX
|
|
||||||
version: 2.18.2
|
|
||||||
- name: GSit
|
|
||||||
version: 6.0.2
|
|
||||||
- name: item-nbt-api-plugin
|
|
||||||
version: 2.*
|
|
||||||
- name: LuckPerm-Bukkit
|
|
||||||
version: 5.4.9
|
|
||||||
- name: PlaceholderAPI
|
|
||||||
version: 2.10.9
|
|
||||||
- name: Plan
|
|
||||||
version: 5.4.0-build-1354
|
|
||||||
- name: PlayerPoints
|
|
||||||
version: 3.0.3
|
|
||||||
- name: ProtocolLib
|
|
||||||
version: 4.8.0
|
|
||||||
- name: SuperVanish
|
|
||||||
version: 6.2.6
|
|
||||||
- name: tabtps-spigot
|
|
||||||
version: 1.3.8
|
|
||||||
- name: Umfrage
|
|
||||||
version: 1.6.3-STABLE
|
|
||||||
- name: Vault
|
|
||||||
- name: VentureChat
|
|
||||||
version: 3.3.2
|
|
||||||
- name: floodgate-bukkit
|
|
||||||
version: 53
|
|
||||||
- name: DisableJoinMessage
|
|
||||||
- name: worldedit-bukkit
|
|
||||||
- name: worldguard-bukkit
|
|
||||||
- name: SkQuery
|
|
||||||
version: 4.1.4
|
|
||||||
- name: Skript
|
|
||||||
version: 2.6.0-beta2
|
|
||||||
- name: skUtilities
|
|
||||||
version: 0.9.2
|
|
||||||
- name: Skungee
|
|
||||||
- name: NametagEdit
|
|
110
repo.py
Normal file
110
repo.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import os
|
||||||
|
from model import *
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
class Repo:
|
||||||
|
def __init__(self, name, config):
|
||||||
|
self.name = name
|
||||||
|
self.config = config
|
||||||
|
self.path = config['path']
|
||||||
|
|
||||||
|
def importPlugin(self, plugin):
|
||||||
|
dest = "{}/{}-{}.jar".format(self.path, plugin.name, plugin.version)
|
||||||
|
shutil.copyfile(plugin.path, dest)
|
||||||
|
|
||||||
|
def plugins(self):
|
||||||
|
plugins = os.listdir(self.path)
|
||||||
|
for pluginFile in plugins:
|
||||||
|
fullPath = os.path.join(self.path, pluginFile)
|
||||||
|
if os.path.isfile(fullPath):
|
||||||
|
try:
|
||||||
|
yield Plugin(fullPath)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def versionsForPlugin(self, name):
|
||||||
|
for plugin in self.plugins():
|
||||||
|
if plugin.name == name:
|
||||||
|
yield plugin.version
|
||||||
|
|
||||||
|
def badFiles(self):
|
||||||
|
plugins = os.listdir(self.path)
|
||||||
|
for pluginFile in plugins:
|
||||||
|
fullPath = os.path.join(self.path, pluginFile)
|
||||||
|
if os.path.isfile(fullPath):
|
||||||
|
try:
|
||||||
|
Plugin(fullPath)
|
||||||
|
except ValueError:
|
||||||
|
yield fullPath
|
||||||
|
|
||||||
|
class Server:
|
||||||
|
def __init__(self, path, pluginSet):
|
||||||
|
self.path = path
|
||||||
|
self.pluginSet = pluginSet
|
||||||
|
|
||||||
|
def updateSymlinkForPlugin(self, plugin, version):
|
||||||
|
pluginFilename = os.path.join(self.path, 'plugins/versions/{}-{}.jar'.format(plugin.name, version))
|
||||||
|
pluginSymlink = os.path.join(self.path, plugin.name + '.jar')
|
||||||
|
linkDst = os.path.relpath(pluginFilename, self.path)
|
||||||
|
|
||||||
|
if os.path.lexists(pluginSymlink):
|
||||||
|
os.unlink(pluginSymlink)
|
||||||
|
os.symlink(linkDst, pluginSymlink)
|
||||||
|
|
||||||
|
def pluginStates(self):
|
||||||
|
managedPluginFilenames = []
|
||||||
|
for plugin in self.pluginSet:
|
||||||
|
compatibleVersions = []
|
||||||
|
pluginLinkName = '{}.jar'.format(plugin.name)
|
||||||
|
managedPluginFilenames.append(pluginLinkName)
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(self.path, pluginLinkName)) and not os.path.islink(os.path.join(self.path, pluginLinkName)):
|
||||||
|
yield SymlinkConflict(plugin)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for installedVersion in self.versionsForPlugin(plugin.name):
|
||||||
|
if installedVersion in plugin.versionSpec:
|
||||||
|
compatibleVersions.append(installedVersion)
|
||||||
|
|
||||||
|
if len(compatibleVersions) == 0:
|
||||||
|
yield MissingVersions(plugin)
|
||||||
|
else:
|
||||||
|
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
||||||
|
currentVersion = self.currentVersionForPlugin(plugin.name)
|
||||||
|
|
||||||
|
if currentVersion == preferredVersion:
|
||||||
|
yield Installed(plugin, currentVersion)
|
||||||
|
else:
|
||||||
|
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
|
||||||
|
|
||||||
|
otherPlugins = os.listdir(self.path)
|
||||||
|
for pluginFile in otherPlugins:
|
||||||
|
if os.path.isfile(os.path.join(self.path, pluginFile)) and pluginFile not in managedPluginFilenames:
|
||||||
|
yield UnmanagedFile(pluginFile)
|
||||||
|
|
||||||
|
def currentVersionForPlugin(self, pluginName):
|
||||||
|
pluginSymlink = os.path.join(self.path, pluginName + '.jar')
|
||||||
|
if not os.path.lexists(pluginSymlink):
|
||||||
|
return None
|
||||||
|
suffix = '.jar'
|
||||||
|
pluginJar = os.path.basename(os.readlink(pluginSymlink))
|
||||||
|
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
|
||||||
|
try:
|
||||||
|
pluginSemver = Version.coerce(jarVersion)
|
||||||
|
except ValueError:
|
||||||
|
pluginSemver = jarVersion
|
||||||
|
return pluginSemver
|
||||||
|
|
||||||
|
def versionsForPlugin(self, pluginName):
|
||||||
|
plugins = os.listdir(os.path.join(self.path, 'plugins', 'versions'))
|
||||||
|
for pluginJar in plugins:
|
||||||
|
if pluginJar.startswith(pluginName):
|
||||||
|
prefix = pluginName + '-'
|
||||||
|
suffix = '.jar'
|
||||||
|
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
|
||||||
|
try:
|
||||||
|
pluginSemver = Version.coerce(jarVersion)
|
||||||
|
except ValueError:
|
||||||
|
pluginSemver = jarVersion
|
||||||
|
yield pluginSemver
|
||||||
|
|
96
server.py
Normal file
96
server.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
from model import *
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
class Server:
|
||||||
|
def __init__(self, name, config):
|
||||||
|
self.name = name
|
||||||
|
self.config = config
|
||||||
|
self.path = config['path']
|
||||||
|
self.pluginPath = self.path+'/plugins'
|
||||||
|
|
||||||
|
def plugins(self):
|
||||||
|
return [PluginSpec(p['name'], p['version']) for p in self.config['plugins']]
|
||||||
|
|
||||||
|
def add_plugin(self, pluginSpec):
|
||||||
|
for plugin in self.config['plugins']:
|
||||||
|
if plugin['name'] == pluginSpec.name:
|
||||||
|
raise KeyError("Cannot add plugin multiple times.")
|
||||||
|
self.config['plugins'].append({'name': pluginSpec.name, 'version': str(pluginSpec.versionSpec)})
|
||||||
|
|
||||||
|
def pluginStates(self, repos):
|
||||||
|
managedPluginFilenames = []
|
||||||
|
for plugin in self.plugins():
|
||||||
|
compatibleVersions = []
|
||||||
|
pluginLinkName = '{}.jar'.format(plugin.name)
|
||||||
|
managedPluginFilenames.append(pluginLinkName)
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(self.pluginPath, pluginLinkName)) and not os.path.islink(os.path.join(self.pluginPath, pluginLinkName)):
|
||||||
|
yield SymlinkConflict(plugin)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for installedVersion in self.versionsForPlugin(plugin.name, repos):
|
||||||
|
if installedVersion in plugin.versionSpec:
|
||||||
|
compatibleVersions.append(installedVersion)
|
||||||
|
|
||||||
|
if len(compatibleVersions) == 0:
|
||||||
|
for repo in repos:
|
||||||
|
for repoPlugin in repo.plugins():
|
||||||
|
if repoPlugin.name == plugin.name and repoPlugin.version in plugin.versionSpec:
|
||||||
|
compatibleVersions.append(repoPlugin)
|
||||||
|
if len(compatibleVersions) == 0:
|
||||||
|
yield MissingVersions(plugin)
|
||||||
|
else:
|
||||||
|
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
||||||
|
yield Available(preferredVersion)
|
||||||
|
else:
|
||||||
|
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
||||||
|
currentVersion = self.currentVersionForPlugin(plugin.name)
|
||||||
|
|
||||||
|
if currentVersion == preferredVersion:
|
||||||
|
yield Installed(plugin, currentVersion)
|
||||||
|
else:
|
||||||
|
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
|
||||||
|
|
||||||
|
otherPlugins = os.listdir(self.pluginPath)
|
||||||
|
for pluginFile in otherPlugins:
|
||||||
|
if os.path.isfile(os.path.join(self.pluginPath, pluginFile)) and pluginFile not in managedPluginFilenames:
|
||||||
|
yield UnmanagedFile(pluginFile)
|
||||||
|
|
||||||
|
def currentVersionForPlugin(self, pluginName):
|
||||||
|
pluginSymlink = os.path.join(self.pluginPath, pluginName + '.jar')
|
||||||
|
if not os.path.lexists(pluginSymlink):
|
||||||
|
return None
|
||||||
|
suffix = '.jar'
|
||||||
|
pluginJar = os.path.basename(os.readlink(pluginSymlink))
|
||||||
|
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
|
||||||
|
try:
|
||||||
|
pluginSemver = Version.coerce(jarVersion)
|
||||||
|
except ValueError:
|
||||||
|
pluginSemver = jarVersion
|
||||||
|
return pluginSemver
|
||||||
|
|
||||||
|
def versionsForPlugin(self, pluginName, repos):
|
||||||
|
plugins = os.listdir(os.path.join(self.pluginPath, 'versions'))
|
||||||
|
for pluginJar in plugins:
|
||||||
|
if pluginJar.startswith(pluginName):
|
||||||
|
prefix = pluginName + '-'
|
||||||
|
suffix = '.jar'
|
||||||
|
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
|
||||||
|
try:
|
||||||
|
pluginSemver = Version.coerce(jarVersion)
|
||||||
|
except ValueError:
|
||||||
|
pluginSemver = jarVersion
|
||||||
|
yield pluginSemver
|
||||||
|
|
||||||
|
def updateSymlinkForPlugin(self, plugin, version):
|
||||||
|
pluginFilename = os.path.join(self.pluginPath, 'versions/{}-{}.jar'.format(plugin.name, version))
|
||||||
|
pluginSymlink = os.path.join(self.pluginPath, plugin.name + '.jar')
|
||||||
|
linkDst = os.path.relpath(pluginFilename, self.pluginPath)
|
||||||
|
|
||||||
|
if os.path.lexists(pluginSymlink):
|
||||||
|
os.unlink(pluginSymlink)
|
||||||
|
os.symlink(linkDst, pluginSymlink)
|
||||||
|
|
||||||
|
def installVersion(self, plugin):
|
||||||
|
dest = os.path.join(self.pluginPath, 'versions/{}-{}.jar'.format(plugin.name, plugin.version))
|
||||||
|
shutil.copyfile(plugin.path, dest)
|
Loading…
Reference in New Issue
Block a user