webvac 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README +120 -0
- data/Rakefile +33 -0
- data/bin/webvac-server +88 -0
- data/bin/webvac-sweep +60 -0
- data/conf/rainbows.rb +7 -0
- data/config.ru +87 -0
- data/doc/LICENSE +668 -0
- data/doc/TODO +15 -0
- data/doc/nginx.example.conf +31 -0
- data/lib/webvac.rb +159 -0
- metadata +101 -0
data/doc/TODO
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Unordered:
|
2
|
+
|
3
|
+
· The closure abuse in Serv precludes using the URL to generate the routes.
|
4
|
+
This needs a fix in order to generalize beyond Pleroma.
|
5
|
+
· Should be easy to stream rather than loading everything into memory,
|
6
|
+
but until then, big-ish files (≈2MB) take a second to get out of venti.
|
7
|
+
Obviously, it'll be faster and more reliable to implement the venti protocol.
|
8
|
+
· Stats and webvac-unsweep. This will allow hot objects to be swapped out of
|
9
|
+
venti.
|
10
|
+
· Implement the venti protocol instead of calling $plan9bin/vac.
|
11
|
+
· Pool venti servers.
|
12
|
+
· Redis does a fine job for the lookup tables, but I cannot shake the feeling
|
13
|
+
that I should be using something else. Maybe I'm just overly nervous about
|
14
|
+
it.
|
15
|
+
· Etags?
|
@@ -0,0 +1,31 @@
|
|
1
|
+
pid /tmp/nginx.pid;
|
2
|
+
error_log /tmp/nginx-error.log info;
|
3
|
+
daemon off;
|
4
|
+
http {
|
5
|
+
include /etc/nginx/mime.types;
|
6
|
+
access_log /tmp/nginx.log;
|
7
|
+
server {
|
8
|
+
listen 8890;
|
9
|
+
server_name localhost;
|
10
|
+
|
11
|
+
# These two location blocks are the interesting part of the config file:
|
12
|
+
location /media {
|
13
|
+
root /media/www;
|
14
|
+
try_files $uri @media;
|
15
|
+
}
|
16
|
+
location @media {
|
17
|
+
proxy_pass http://localhost:8891;
|
18
|
+
proxy_set_header Host $http_host;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
# I have no idea why *all* of this is required:
|
23
|
+
client_body_temp_path /tmp;
|
24
|
+
proxy_temp_path /tmp;
|
25
|
+
fastcgi_temp_path /tmp;
|
26
|
+
uwsgi_temp_path /tmp;
|
27
|
+
scgi_temp_path /tmp;
|
28
|
+
}
|
29
|
+
events {
|
30
|
+
worker_connections 1024;
|
31
|
+
}
|
data/lib/webvac.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
%w(
|
2
|
+
redic
|
3
|
+
magic
|
4
|
+
json
|
5
|
+
time
|
6
|
+
cgi
|
7
|
+
).each &method(:require)
|
8
|
+
|
9
|
+
# The namespace for WebVac. See the README.
|
10
|
+
module WebVac
|
11
|
+
# Config object, intended to be used as a singleton.
|
12
|
+
class Config
|
13
|
+
# The default config options. See the README.
|
14
|
+
Defaults = {
|
15
|
+
redis_url: "redis://localhost:6379/0",
|
16
|
+
|
17
|
+
server_path_strip: "/media",
|
18
|
+
server_path_prepend: "/media/block/fse",
|
19
|
+
|
20
|
+
venti_server: 'localhost',
|
21
|
+
|
22
|
+
plan9bin: '/opt/plan9/bin',
|
23
|
+
|
24
|
+
mime_substitutions: {
|
25
|
+
'text/html' => 'text/plain',
|
26
|
+
},
|
27
|
+
}
|
28
|
+
attr_accessor *Defaults.keys
|
29
|
+
|
30
|
+
# The sorted list of places where we will look for config files
|
31
|
+
# to load.
|
32
|
+
ConfigPaths = [
|
33
|
+
ENV['WEBVAC_CONFIG'],
|
34
|
+
"./config/webvac.json",
|
35
|
+
"#{ENV['HOME']}/.webvac.json",
|
36
|
+
"/etc/webvac.json",
|
37
|
+
].compact
|
38
|
+
|
39
|
+
# Reads/parses config and instantiates an object
|
40
|
+
def self.load
|
41
|
+
f = ConfigPaths.find { |f| File.readable?(f) }
|
42
|
+
cfg = if f
|
43
|
+
JSON.parse File.read(f)
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
new cfg
|
48
|
+
end
|
49
|
+
|
50
|
+
# Takes a config, replaces the defaults with it.
|
51
|
+
# Will throw exceptions if you give it a bad config, you should probably
|
52
|
+
# just call Config.load.
|
53
|
+
def initialize cfg
|
54
|
+
Defaults.each { |k,v|
|
55
|
+
send("#{k}=", v)
|
56
|
+
}
|
57
|
+
cfg.each { |k,v|
|
58
|
+
send("#{k}=", v)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def path_fixup path
|
63
|
+
@_path_rx ||= /^#{Regexp.escape(server_path_strip)}/
|
64
|
+
path.sub(@_path_rx, server_path_prepend)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Stateless-ish client for venti.
|
69
|
+
# I completely punted on implementing a venti client, so it just calls
|
70
|
+
# the vac/unvac binaries. Does the job!
|
71
|
+
class Vac
|
72
|
+
attr_reader :config
|
73
|
+
|
74
|
+
# Takes an instance of Config.
|
75
|
+
def initialize cfg
|
76
|
+
@config = cfg
|
77
|
+
end
|
78
|
+
|
79
|
+
def save! fn
|
80
|
+
contents = File.read(fn)
|
81
|
+
pi, po = IO.pipe
|
82
|
+
io = IO.popen(
|
83
|
+
{'venti' => config.venti_server},
|
84
|
+
["#{config.plan9bin}/vac", '-i', File.basename(fn)],
|
85
|
+
in: pi
|
86
|
+
).tap { |io| Thread.new { Process.wait(io.pid) } }
|
87
|
+
po.write contents
|
88
|
+
po.close
|
89
|
+
io.read.chomp.sub(/^vac:/, '')
|
90
|
+
end
|
91
|
+
|
92
|
+
def load! vac
|
93
|
+
unless /^vac:[a-f0-9]{40}$/.match(vac)
|
94
|
+
raise ArgumentError, "#{vac.inspect} not a vac score?"
|
95
|
+
end
|
96
|
+
IO.popen(
|
97
|
+
{'venti' => config.venti_server},
|
98
|
+
["#{config.plan9bin}/unvac", '-c', vac]
|
99
|
+
).tap { |io| Thread.new { Process.wait(io.pid) } }.read
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Sits in front of Redis (just Redis right now), and handles the mapping
|
104
|
+
# of vac hashes to pathnames, as well as the metadata (in JSON and in the
|
105
|
+
# form of HTTP headers, which allows HEAD requests to be cheap). Also does
|
106
|
+
# some of the bookkeeping necessary for that, like the interaction with
|
107
|
+
# libmagic.
|
108
|
+
#
|
109
|
+
# Relatively threadsafe, but maintains one Redis connection per active
|
110
|
+
# thread (created on demand).
|
111
|
+
class Table
|
112
|
+
attr_reader :config
|
113
|
+
|
114
|
+
# Takes an instance of Config.
|
115
|
+
def initialize cfg
|
116
|
+
@config = cfg
|
117
|
+
end
|
118
|
+
|
119
|
+
# Takes a filename, returns the filename's metadata. Stateless-ish.
|
120
|
+
def fn2md f
|
121
|
+
s = File.stat(f)
|
122
|
+
m = {
|
123
|
+
'Content-Type' => Magic.guess_file_mime_type(f),
|
124
|
+
'Content-Length' => s.size.to_s,
|
125
|
+
'Last-Modified' => s.mtime.rfc822,
|
126
|
+
} rescue nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def meta_save! fn, sc
|
130
|
+
md = fn2md(fn)
|
131
|
+
return unless md
|
132
|
+
redis.call 'HSET', 'score2md', sc, md.to_json
|
133
|
+
end
|
134
|
+
|
135
|
+
def metadata score
|
136
|
+
# Overall, doesn't really matter if this fails.
|
137
|
+
JSON.parse(
|
138
|
+
redis.call('HGET', 'score2md', score.sub(/^vac:/, ''))
|
139
|
+
) rescue nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def rec_score! fn, sc
|
143
|
+
redis.call 'HSET', 'path2score', fn, sc
|
144
|
+
end
|
145
|
+
|
146
|
+
def redis
|
147
|
+
Thread.current[:webvac_redis] ||= Redic.new(config.redis_url)
|
148
|
+
end
|
149
|
+
|
150
|
+
def path2score p
|
151
|
+
r = redis.call 'HGET', 'path2score', p
|
152
|
+
return "vac:#{r}" if r
|
153
|
+
end
|
154
|
+
|
155
|
+
def guess_mime contents
|
156
|
+
Magic.guess_string_mime_type(contents)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: webvac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pete
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redic
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: watts
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email: pete@debu.gs
|
57
|
+
executables:
|
58
|
+
- webvac-sweep
|
59
|
+
- webvac-server
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files:
|
62
|
+
- doc/LICENSE
|
63
|
+
- doc/TODO
|
64
|
+
- doc/nginx.example.conf
|
65
|
+
- README
|
66
|
+
files:
|
67
|
+
- README
|
68
|
+
- Rakefile
|
69
|
+
- bin/webvac-server
|
70
|
+
- bin/webvac-sweep
|
71
|
+
- conf/rainbows.rb
|
72
|
+
- config.ru
|
73
|
+
- doc/LICENSE
|
74
|
+
- doc/TODO
|
75
|
+
- doc/nginx.example.conf
|
76
|
+
- lib/webvac.rb
|
77
|
+
homepage: http://github.com/pete/webvac
|
78
|
+
licenses:
|
79
|
+
- AGPL-3.0
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.6.14.1
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: UGC management/backup using venti
|
101
|
+
test_files: []
|