Skip to content

Commit 423443f

Browse files
committed
🎒
0 parents  commit 423443f

15 files changed

+644
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*.cr]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 2
9+
trim_trailing_whitespace = true

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/docs/
2+
/lib/
3+
/bin/
4+
/.shards/
5+
*.dwarf
6+
.rucksack
7+
.rucksack.toc
8+
9+
# Libraries don't need dependency lock
10+
# Dependencies will be locked in applications that use them
11+
/shard.lock

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 [email protected]
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.PHONY: test
2+
3+
test:
4+
@time test/test.sh

README.md

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Rucksack
2+
3+
Attach static files to your compiled crystal binary
4+
and access them at runtime.
5+
6+
The attached files are not loaded into memory at any
7+
point in time. Reading them at runtime has about the
8+
same performance characteristics as reading them from
9+
the local filesystem.
10+
11+
Rucksack is therefore suitable for true Single File Deployments
12+
with virtually zero runtime overhead.
13+
14+
## Installation
15+
16+
1. Add the dependency to your `shard.yml`:
17+
18+
```yaml
19+
dependencies:
20+
rucksack:
21+
github: busyloop/rucksack
22+
```
23+
24+
2. Run `shards install`
25+
26+
3. Add the following lines to your `.gitignore`:
27+
28+
```
29+
.rucksack
30+
.rucksack.toc
31+
```
32+
33+
## Usage
34+
35+
To get started best have a peek at the included [webserver example](examples/).
36+
37+
Here is the code:
38+
39+
```crystal
40+
require "http/server"
41+
require "rucksack"
42+
43+
server = HTTP::Server.new do |context|
44+
path = context.request.path
45+
path = "/index.html" if path == "/"
46+
path = "./webroot#{path}"
47+
48+
begin
49+
# Here we read the requested file from the Rucksack
50+
# and write it to the HTTP response. By default Rucksack
51+
# falls back to direct filesystem access in case the
52+
# executable has no Rucksack attached.
53+
rucksack(path).read(context.response.output)
54+
rescue
55+
context.response.status = HTTP::Status.new(404)
56+
context.response.print "404 not found :("
57+
end
58+
end
59+
60+
address = server.bind_tcp 8080
61+
puts "Listening on http://#{address}"
62+
server.listen
63+
64+
# Here we statically reference the files to be included
65+
# once - otherwise Rucksack wouldn't know what to pack.
66+
{% for name in `find ./webroot -type f`.split('\n') %}
67+
rucksack({{name}})
68+
{% end %}
69+
70+
```
71+
72+
You can develop and test code that uses Rucksack in the same way as any other crystal code.
73+
74+
75+
76+
## Packing the Rucksack
77+
78+
After compiling your final binary for deployment, a small extra step
79+
is needed to make it self-contained:
80+
81+
```
82+
crystal build --release webserver.cr
83+
cat .rucksack >>webserver
84+
```
85+
86+
The `.rucksack`-file that we append here
87+
is generated during compilation and contains all
88+
files that you referenced with the rucksack()-macro.
89+
90+
The resulting `webserver` executable is now self-contained
91+
and does not require the referenced files to be
92+
present in the filesystem anymore.
93+
94+
95+
96+
## Startup and runtime behavior
97+
98+
By default Rucksack operates in mode 0 (see below).
99+
100+
You can alter its behavior by setting the env var `RUCKSACK_MODE`
101+
to one of the following values:
102+
103+
### `RUCKSACK_MODE=0` (default)
104+
105+
* Rucksack index is read at startup (very fast)
106+
107+
* The rucksack() macro falls back to direct filesystem access
108+
if the rucksack is missing or doesn't contain the requested file
109+
110+
* File checksums are verified once on first access
111+
112+
* If a requested file can be found neither in the Rucksack nor
113+
in the local filesystem then `Rucksack::FileNotFound` is raised.
114+
115+
* This mode ensures your app works not only when the Rucksack has
116+
been appended to your executable but also when it's missing
117+
and the files are present in the filesystem.
118+
119+
This is the preferred mode during development as e.g. `crystal spec` and `crystal run` will work as expected.
120+
121+
122+
### `RUCKSACK_MODE=1` (for production)
123+
124+
* Rucksack index is read at startup (very fast)
125+
126+
* Application aborts with exit code 42 if Rucksack is missing or corrupt
127+
128+
* File checksums are verified once on first access
129+
130+
* If a requested file can not be found in the Rucksack
131+
then `Rucksack::FileNotFound` is raised
132+
133+
* **Prefer this mode for CI and production**. It ensures your app aborts
134+
at startup in case the Rucksack is missing, rather than later when
135+
trying to access the files.
136+
137+
138+
### `RUCKSACK_MODE=2` (for the paranoid)
139+
140+
* This is like mode 1, except it also verifies the checksums
141+
of all attached files at startup
142+
143+
* Application aborts with exit code 42 if Rucksack is missing or corrupt
144+
or if any file in the Rucksack is corrupt
145+
146+
* If you are worried about potential corruption and can tolerate
147+
a slightly longer startup delay then this is the mode for you
148+
149+
## Contributing
150+
151+
1. Fork it (<https://github.com/busyloop/rucksack/fork>)
152+
2. Create your feature branch (`git checkout -b my-new-feature`)
153+
3. Commit your changes (`git commit -am 'Add some feature'`)
154+
4. Push to the branch (`git push origin my-new-feature`)
155+
5. Create a new Pull Request
156+

examples/webroot/css/styles.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
body {
2+
background: #ddffdd;
3+
}

examples/webroot/index.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Rucksack demo</title>
5+
<link rel="stylesheet" type="text/css" href="/css/styles.css">
6+
</head>
7+
<body>
8+
Hello world :-)
9+
</body>
10+
</html>

examples/webserver.cr

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Compile with:
2+
#
3+
# crystal build --release webserver.cr && cat .rucksack >>webserver
4+
5+
# The resulting binary is then self-contained
6+
# and does not need the referenced files to be
7+
# present in the filesystem anymore.
8+
9+
require "http/server"
10+
require "rucksack"
11+
12+
server = HTTP::Server.new do |context|
13+
path = context.request.path
14+
path = "/index.html" if path == "/"
15+
path = "./webroot#{path}"
16+
17+
begin
18+
rucksack(path).read(context.response.output)
19+
rescue
20+
context.response.status = HTTP::Status.new(404)
21+
context.response.print "404 not found :("
22+
end
23+
end
24+
25+
address = server.bind_tcp 8080
26+
puts "Listening on http://#{address}"
27+
server.listen
28+
29+
# Since we look up files dynamically above
30+
# we have to statically reference them once,
31+
# otherwise Rucksack wouldn't know what to pack.
32+
{% for name in `find ./webroot -type f`.split('\n') %}
33+
rucksack({{name}})
34+
{% end %}
35+

fixtures/cat1.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
_._ _,-'""`-._
2+
(,-.`._,'( |\`-/|
3+
`-.-' \ )-`( , o o)
4+
`- \`_`"'-

fixtures/cat2.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/\_____/\
2+
/ o o \
3+
( == ^ == )
4+
) (
5+
( )
6+
( ( ) ( ) )
7+
(__(__)___(__)__)

fixtures/cat3.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
,-. _,---._ __ / \
2+
/ ) .-' `./ / \
3+
( ( ,' `/ /|
4+
\ `-" \'\ / |
5+
`. , \ \ / |
6+
/`. ,'-`----Y |
7+
( ; | '
8+
| ,-. ,-' | /
9+
| | ( | hjw | /
10+
) | \ `.___________|/
11+
`--' `--'

shard.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: rucksack
2+
version: 1.0.0
3+
description: Attach static files to your compiled crystal binary and access them at runtime.
4+
5+
authors:
6+
7+
8+
crystal: 0.32.1
9+
10+
license: MIT
11+

0 commit comments

Comments
 (0)