Skip to content

Commit 30d6918

Browse files
committed
init
0 parents  commit 30d6918

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2018
-0
lines changed

.dockerignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.idea
2+
data
3+
temp
4+
node_modules
5+
app/node_modules
6+
npm-debug.log
7+
scripts
8+
docs

.editorconfig

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# EditorConfig helps developers define and maintain consistent
2+
# coding styles between different editors and IDEs
3+
# editorconfig.org
4+
5+
root = true
6+
7+
[*]
8+
9+
indent_style = space
10+
indent_size = 2
11+
end_of_line = lf
12+
charset = utf-8
13+
trim_trailing_whitespace = true
14+
insert_final_newline = true
15+
16+
[*.md, *.pug]
17+
trim_trailing_whitespace = false
18+
19+
[*.yml]
20+
indent_style = space

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.idea
2+
data
3+
temp
4+
node_modules
5+
npm-debug.log

.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v7

Dockerfile

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
FROM node:7-alpine
2+
3+
ENV PSITRANSFER_UPLOAD_DIR=/data \
4+
NODE_ENV=production
5+
6+
MAINTAINER Christoph Wiechert <[email protected]>
7+
8+
WORKDIR /app
9+
10+
ADD *.js package.json README.md /app/
11+
ADD lib /app/lib
12+
ADD app /app/app
13+
ADD public /app/public
14+
15+
# Rebuild the frontend apps
16+
RUN cd app && \
17+
NODE_ENV=dev npm install --quiet 1>/dev/null && \
18+
npm run build && \
19+
cd .. && rm -rf app
20+
21+
# Install backend dependencies
22+
RUN mkdir /data && \
23+
chown node /data && \
24+
npm install --quiet 1>/dev/null
25+
26+
EXPOSE 3000
27+
VOLUME ["/data"]
28+
29+
USER node
30+
31+
HEALTHCHECK CMD wget -O /dev/null -q http://localhost:3000
32+
33+
CMD ["node", "app.js"]

LICENSE

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Copyright 2017 Christoph Wiechert
2+
3+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4+
5+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6+
7+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8+
9+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# PsiTransfer
2+
3+
Simple open source self-hosted file sharing solution.
4+
5+
* Supports many and very big files (Streams ftw)
6+
* Resumable up- and downloads ([TUS](https://tus.io))
7+
* Set an expire-time for your upload bucket
8+
* One-time downloads
9+
* Password protected downloads ([AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard))
10+
* Requires Node >=7.4
11+
12+
![Screenshot](https://raw.githubusercontent.com/psi-4ward/psitransfer/docs/psitransfer.gif)
13+
14+
**Demo**: https://transfer.psi.cx
15+
16+
## Quickstart
17+
18+
### Docker (recommended)
19+
```bash
20+
$ docker run -p 0.0.0.0:3000:3000 -v $PWD/data:/data psitrax/psitransfer
21+
# data volume needs UID 1000
22+
$ sudo chown -R 1000 $PWD/data
23+
```
24+
25+
### Manual
26+
27+
```bash
28+
# Be sure to have NodeJS >= 7.4
29+
$ node -v
30+
v7.4.0
31+
32+
# Download and extract latest release package from
33+
# https://github.com/psi-4ward/psitransfer/releases
34+
35+
# Install dependencies and start the app
36+
$ NODE_ENV=production npm install
37+
$ npm start
38+
```
39+
40+
### Configuration
41+
42+
There are some configs in `config.js` like port and data-dir.
43+
You can:
44+
* Edit the `config.js` **(not recommend)**
45+
* Add a `config.production.js` where `production` is the value from `NODE_ENV`
46+
See `config.dev.js`
47+
* Define environment Variables like `PSITRANSFER_UPLOAD_DIR`
48+
49+
### Customization
50+
51+
`public/upload.html` and `download.html` are kept simple.
52+
You can alter these files and add your logo and styles.
53+
The following elements are mandatory:
54+
`common.js` and respectively `upload.js`, `download.js` as well as `<div id="upload">`, `<div id="download">`
55+
Please keep a footnote like *Powered by PsiTransfer* :)
56+
57+
### Debug
58+
59+
Psitransfer uses [debug](https://github.com/visionmedia/debug):
60+
61+
```bash
62+
DEBUG=psitransfer:* npm start
63+
```
64+
65+
## License
66+
67+
[BSD](LICENSE)
68+

app.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
const config = require('./config');
3+
const app = require('./lib/endpoints');
4+
5+
/**
6+
* Naming:
7+
* sid: Group of files
8+
* key: File
9+
* fid: {sid}++{key}
10+
*/
11+
12+
const server = app.listen(config.port, config.iface, () => {
13+
console.log(`PsiTransfer listening on http://${config.iface}:${config.port}`);
14+
});
15+
16+
17+
// graceful shutdown
18+
function shutdown() {
19+
console.log('PsiTransfer shutting down...');
20+
server.close(() => {
21+
process.exit(0);
22+
});
23+
setTimeout(function() {
24+
console.log('Could not close connections in time, forcefully shutting down');
25+
process.exit(0);
26+
}, 180 * 1000);
27+
}
28+
process.on('SIGTERM', shutdown);
29+
process.on('SIGINT', shutdown);

app/.babelrc

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"presets": [
3+
[
4+
"env",
5+
{
6+
"modules": false
7+
}
8+
],
9+
"stage-2"
10+
],
11+
"plugins": [
12+
"transform-runtime"
13+
],
14+
"comments": false
15+
}

app/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
node_modules/
3+
dist/
4+
npm-debug.log

app/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# PsiTransfer Upload / Download App
2+
3+
## Build Setup
4+
5+
``` bash
6+
# install dependencies
7+
npm install
8+
9+
# serve with hot reload at localhost:8080
10+
npm run dev
11+
12+
# build for production with minification
13+
npm run build
14+
```

app/package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "psitransfer",
3+
"description": "A Vue.js project",
4+
"version": "1.0.0",
5+
"author": "Christoph Wiechert <[email protected]>",
6+
"private": true,
7+
"scripts": {
8+
"dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --host 0.0.0.0",
9+
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
10+
},
11+
"dependencies": {
12+
"babel-polyfill": "^6.23.0",
13+
"crypto-js": "^3.1.9-1",
14+
"drag-drop": "^2.13.2",
15+
"tus-js-client": "^1.4.3",
16+
"uuid": "^3.0.1",
17+
"vue": "^2.2.6",
18+
"vuex": "^2.3.1"
19+
},
20+
"devDependencies": {
21+
"babel-core": "^6.24.1",
22+
"babel-loader": "^6.4.1",
23+
"babel-plugin-transform-runtime": "^6.23.0",
24+
"babel-preset-env": "^1.3.2",
25+
"babel-preset-stage-2": "^6.22.0",
26+
"cross-env": "^4.0.0",
27+
"css-loader": "^0.28.0",
28+
"file-loader": "^0.11.1",
29+
"pug": "^2.0.0-beta.12",
30+
"vue-loader": "^11.3.4",
31+
"vue-template-compiler": "^2.2.6",
32+
"webpack": "^2.4.1",
33+
"webpack-dev-server": "^2.4.2"
34+
}
35+
}

app/src/Download.vue

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<template lang="pug">
2+
.download-app
3+
.btn-group(style='position: absolute; top: 15px; right: 15px;')
4+
a.btn.btn-sm.btn-info(@click='newSession()', title='New Upload')
5+
i.fa.fa-fw.fa-cloud-upload
6+
| new upload
7+
.alert.alert-danger(v-show="error")
8+
strong
9+
i.fa.fa-exclamation-triangle
10+
| {{ error }}
11+
.well(v-if='needsPassword')
12+
h3(style='margin-top: 0') Password
13+
.form-group
14+
input.form-control(type='password', v-model='password')
15+
p.text-danger(v-show='passwordWrong')
16+
strong Access denied!
17+
|
18+
button.btn.btn-primary(:disabled='password.length<1', @click='decrypt()')
19+
i.fa.fa-key
20+
| decrypt
21+
.panel.panel-primary(v-if='!needsPassword')
22+
.panel-heading Files
23+
.panel-body
24+
table.table.table-hover.table-striped(style='margin-bottom: 0')
25+
tbody
26+
tr(v-for='file in files', style='cursor: pointer', @click='download(file)')
27+
td(style='width: 60px')
28+
file-icon(:file='file')
29+
td
30+
p
31+
clipboard.pull-right(:value='host + file.url', @change='copied(file, $event)', title='Copy to clipboard', style='margin-left: 5px')
32+
a
33+
i.fa.fa-fw.fa-copy
34+
i.fa.fa-check.text-success.pull-right(v-show='file.downloaded')
35+
|
36+
strong {{ file.metadata.name }}
37+
|
38+
small ({{ humanFileSize(file.size) }})
39+
p {{ file.metadata.comment }}
40+
</template>
41+
42+
43+
<script>
44+
"use strict";
45+
import AES from 'crypto-js/aes';
46+
import encUtf8 from 'crypto-js/enc-utf8';
47+
48+
import FileIcon from './common/FileIcon.vue';
49+
import Clipboard from './common/Clipboard.vue';
50+
51+
export default {
52+
name: 'app',
53+
components: { FileIcon, Clipboard },
54+
data () {
55+
return {
56+
files: [],
57+
sid: document.location.pathname.substr(1),
58+
passwordWrong: false,
59+
needsPassword: false,
60+
password: '',
61+
content: '',
62+
error: '',
63+
host: document.location.protocol + '//' + document.location.host
64+
}
65+
},
66+
methods: {
67+
download(file) {
68+
if(file.downloaded && file.metadata.retention === 'one-time') {
69+
alert('One-Time Download: File is not available anymore.');
70+
return;
71+
}
72+
document.location.href = file.url;
73+
file.downloaded = true;
74+
},
75+
76+
copied(file, $event) {
77+
file.downloaded = $event === 'copied';
78+
},
79+
80+
decrypt() {
81+
this.passwordWrong = false;
82+
this.files = this.files.map(item => {
83+
if(typeof item === 'object') return item;
84+
const d = AES.decrypt(item, this.password);
85+
try {
86+
return Object.assign(
87+
JSON.parse(d.toString(encUtf8)),
88+
{downloaded: false}
89+
);
90+
} catch(e) {
91+
this.passwordWrong = true;
92+
return item;
93+
}
94+
});
95+
if(!this.passwordWrong) {
96+
this.needsPassword = false;
97+
this.password = '';
98+
}
99+
},
100+
101+
humanFileSize(fileSizeInBytes) {
102+
let i = -1;
103+
const byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
104+
do {
105+
fileSizeInBytes = fileSizeInBytes / 1024;
106+
i++;
107+
}
108+
while(fileSizeInBytes > 1024);
109+
return Math.max(fileSizeInBytes, 0.01).toFixed(2) + byteUnits[i];
110+
},
111+
112+
newSession() {
113+
document.location.href = '/';
114+
}
115+
},
116+
117+
beforeMount() {
118+
const xhr = new XMLHttpRequest();
119+
xhr.open('GET', '/' + this.sid + '.json');
120+
xhr.onload = () => {
121+
if(xhr.status === 200) {
122+
try {
123+
this.files = JSON.parse(xhr.responseText).map(f => {
124+
if(typeof f !== 'object') {
125+
this.needsPassword = true;
126+
return f;
127+
}
128+
return Object.assign(f, {downloaded: false});
129+
});
130+
} catch(e) {
131+
this.error = e.toString();
132+
}
133+
} else {
134+
this.error = `${xhr.status} ${xhr.statusText}: ${xhr.responseText}`;
135+
}
136+
};
137+
xhr.send();
138+
}
139+
}
140+
</script>

0 commit comments

Comments
 (0)