mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-15 21:04:46 +01:00
cleanup (tunnel): deprecate step 2 of the setup
This commit is contained in:
parent
46cde8a166
commit
f15cd1959b
4 changed files with 14 additions and 341 deletions
|
|
@ -67,31 +67,6 @@ export class SetupPage extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onExposeInstance(choice, done){
|
||||
this.setState({busy: true});
|
||||
return Config.all().then((config) => {
|
||||
config = FormObjToJSON(config);
|
||||
config.connections = window.CONFIG.connections;
|
||||
switch(choice){
|
||||
case "tunnel":
|
||||
config.features.server.enable_tunnel = true;
|
||||
break;
|
||||
default:
|
||||
config.features.server.enable_tunnel = false;
|
||||
break;
|
||||
}
|
||||
Config.save(config, false)
|
||||
.then(() => this.setState({busy: false}, done))
|
||||
.catch((err) => {
|
||||
notify.send(err && err.message, "error");
|
||||
this.setState({busy: false});
|
||||
});
|
||||
}).catch((err) => {
|
||||
notify.send(err && err.message, "error");
|
||||
this.setState({busy: false});
|
||||
});
|
||||
}
|
||||
|
||||
enableLog(value){
|
||||
Config.all().then((config) => {
|
||||
config = FormObjToJSON(config);
|
||||
|
|
@ -111,7 +86,7 @@ export class SetupPage extends React.Component {
|
|||
"name_failure": "SSL is not configured properly",
|
||||
"pass": window.location.protocol !== "http:",
|
||||
"severe": true,
|
||||
"message": "This can lead to data leaks. Please use a SSL certificate or expose your instance via a filestash domain"
|
||||
"message": "This can lead to data leaks. Please use a SSL certificate"
|
||||
}, {
|
||||
"name_success": "Application is running as '" + objectGet(config, ["constant", "user", "value"]) + "'",
|
||||
"name_failure": "Application is running as root",
|
||||
|
|
@ -137,22 +112,13 @@ export class SetupPage extends React.Component {
|
|||
this.setState({busy: false});
|
||||
});
|
||||
}
|
||||
tunnelCall(){
|
||||
this.setState({busy: true});
|
||||
return Config.all().then((config) => {
|
||||
//this.setState({busy: false});
|
||||
return objectGet(config, ["features", "server", "tunnel_url", "value"]);
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="component_setup">
|
||||
<MultiStepForm loading={this.state.busy}
|
||||
onAdminPassword={this.onAdminPassword.bind(this)}
|
||||
onExposeInstance={this.onExposeInstance.bind(this) }
|
||||
summaryCall={this.summaryCall.bind(this)}
|
||||
tunnelCall={this.tunnelCall.bind(this)} />
|
||||
summaryCall={this.summaryCall.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -166,73 +132,29 @@ class MultiStepForm extends React.Component {
|
|||
current: parseInt(window.location.hash.replace("#", "")) || 0,
|
||||
answer_password: "",
|
||||
has_answered_password: false,
|
||||
answer_expose: "",
|
||||
has_answered_expose: false,
|
||||
deps: [],
|
||||
redirect_uri: null,
|
||||
working_message: "Working"
|
||||
deps: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if(this.state.current == 2){
|
||||
this.fetchDependencies();
|
||||
if(this.state.current === 1){
|
||||
this.props.summaryCall().then((deps) => {
|
||||
this.setState({deps: deps});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onAdminPassword(e){
|
||||
e.preventDefault();
|
||||
this.props.onAdminPassword(this.state.answer_password, () => {
|
||||
this.setState({current: 1, has_answered_password: true});
|
||||
});
|
||||
}
|
||||
|
||||
onExposeInstance(value, e){
|
||||
e.preventDefault();
|
||||
this.setState({answer_expose: value});
|
||||
this.props.onExposeInstance(value, () => {
|
||||
if(value === "tunnel"){
|
||||
const waitForDomain = (count = 0) => {
|
||||
return this.props.tunnelCall().then((url) => {
|
||||
if(url && /\.filestash\.app$/.test(url) === true){
|
||||
return Promise.resolve(url);
|
||||
}
|
||||
if(count > 10){
|
||||
this.setState({working_message: "Building your domain"});
|
||||
}else if(count > 30){
|
||||
this.setState({working_message: "Processing ."+".".repeat(count % 3)});
|
||||
}
|
||||
if(count >= 60){
|
||||
return Promise.reject({message: "Couldn't create a domain name"});
|
||||
}
|
||||
return new Promise((done) => window.setTimeout(done, 1000))
|
||||
.then(() => waitForDomain(count + 1));
|
||||
});
|
||||
};
|
||||
waitForDomain().then((url) => {
|
||||
this.setState({redirect_uri: url});
|
||||
}).catch((err) => {
|
||||
window.location.hash = "#2";
|
||||
window.location.reload();
|
||||
});
|
||||
} else {
|
||||
this.setState({current: 2, has_answered_expose: true}, () => {
|
||||
this.onStepChange(2);
|
||||
});
|
||||
}
|
||||
this.setState({has_answered_password: true});
|
||||
this.onStepChange(1);
|
||||
});
|
||||
}
|
||||
|
||||
onStepChange(n){
|
||||
this.setState({current: n});
|
||||
if(n === 2){
|
||||
this.fetchDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
fetchDependencies() {
|
||||
this.props.summaryCall().then((deps) => {
|
||||
this.setState({deps: deps});
|
||||
this.setState({current: n}, () => {
|
||||
if(n === 1) this.componentDidMount();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -242,11 +164,11 @@ class MultiStepForm extends React.Component {
|
|||
return (
|
||||
<div id="step1">
|
||||
<FormStage navleft={false} navright={this.state.has_answered_password === true} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
||||
Step 1/2: Secure your instance
|
||||
Admin Password
|
||||
</FormStage>
|
||||
<ReactCSSTransitionGroup transitionName="stepper-form" transitionEnterTimeout={600} transitionAppearTimeout={600} transitionAppear={true} transitionEnter={true} transitionLeave={false}>
|
||||
<div key={this.state.current}>
|
||||
<p>Create your admin password: </p>
|
||||
<p>Create your instance admin password: </p>
|
||||
<form onSubmit={this.onAdminPassword.bind(this)}>
|
||||
<Input ref="$input" type="password" placeholder="Password" value={this.state.answer_password} onChange={(e) => this.setState({answer_password: e.target.value})}/>
|
||||
<Button theme="transparent">
|
||||
|
|
@ -261,45 +183,6 @@ class MultiStepForm extends React.Component {
|
|||
} else if(this.state.current === 1) {
|
||||
return (
|
||||
<div id="step2">
|
||||
<FormStage navleft={true} navright={this.state.has_answered_expose} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
||||
Step 2/2: Expose your instance to the internet ?
|
||||
</FormStage>
|
||||
<ReactCSSTransitionGroup transitionName="stepper-form" transitionEnterTimeout={600} transitionAppearTimeout={600} transitionAppear={true} transitionEnter={true} transitionLeave={false}>
|
||||
<div key={this.state.current}>
|
||||
<NgIf cond={this.state.redirect_uri !== null}>
|
||||
<div style={{textAlign: "center"}}>
|
||||
Your instance is available at <a href={this.state.redirect_uri}>{this.state.redirect_uri}</a>.<br/>
|
||||
You will be redirected in <Countdown max={9} onZero={() => window.location.href = this.state.redirect_uri} /> seconds
|
||||
</div>
|
||||
</NgIf>
|
||||
<NgIf cond={!this.props.loading && this.state.redirect_uri === null}>
|
||||
<form onSubmit={this.onExposeInstance.bind(this, "skip")}>
|
||||
<label className={this.state.answer_expose === "nothing" ? "active" : ""}>
|
||||
<input type="radio" name="expose" value="nothing" checked={this.state.answer_expose === "nothing"} onChange={this.onExposeInstance.bind(this, "nothing")}/>
|
||||
No, don't expose anything to the internet
|
||||
</label>
|
||||
<label className={this.state.answer_expose === "tunnel" ? "active" : ""}>
|
||||
<input type="radio" name="expose" value="tunnel" checked={this.state.answer_expose === "tunnel"} onChange={this.onExposeInstance.bind(this, "tunnel")}/>
|
||||
Yes, and make it available via a filestash subdomain - eg: https://user-me.filestash.app
|
||||
</label>
|
||||
<label className={this.state.answer_expose === "skip" ? "active" : ""}>
|
||||
<input type="radio" name="expose" value="skip" checked={this.state.answer_expose === "skip"} onChange={this.onExposeInstance.bind(this, "skip")}/>
|
||||
Skip if you're a wizard when it comes to SSL certificates and port forwarding
|
||||
</label>
|
||||
</form>
|
||||
</NgIf>
|
||||
<NgIf cond={!!this.props.loading && this.state.redirect_uri === null}>
|
||||
<Loader/>
|
||||
<div style={{textAlign: "center"}}>{this.state.working_message}</div>
|
||||
</NgIf>
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
{hideMenu}
|
||||
</div>
|
||||
);
|
||||
} else if(this.state.current === 2) {
|
||||
return (
|
||||
<div id="step3">
|
||||
<FormStage navleft={true} navright={false} current={this.state.current} onStepChange={this.onStepChange.bind(this)}>
|
||||
Summary
|
||||
</FormStage>
|
||||
|
|
@ -339,33 +222,6 @@ const FormStage = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
class Countdown extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = { count: props.max };
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.timeout = window.setInterval(() => {
|
||||
if(this.state.count - 1 >= 0){
|
||||
this.setState({count: this.state.count - 1}, () => {
|
||||
if(this.state.count === 0) this.props.onZero();
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.clearInterval(this.timeout);
|
||||
}
|
||||
|
||||
render(){
|
||||
return(
|
||||
<span>{this.state.count}</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function objectGet(obj, paths){
|
||||
let value = obj;
|
||||
for(let i=0; i<paths.length; i++){
|
||||
|
|
|
|||
|
|
@ -29,18 +29,6 @@
|
|||
@include inlinedInputWithSubmit();
|
||||
}
|
||||
#step2{
|
||||
label{
|
||||
display: block;
|
||||
background: #f2f3f5;
|
||||
margin: 10px 0;
|
||||
padding: 10px 10px;
|
||||
border-radius: 3px;
|
||||
input[type="radio"]{ display: none; }
|
||||
transition: background 0.05s ease;
|
||||
&:hover, &.active{ background: var(--emphasis-primary); }
|
||||
}
|
||||
}
|
||||
#step3{
|
||||
.component_dependency_installed{
|
||||
margin: 10px 0;
|
||||
padding: 10px 10px;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_tunnel"
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_http"
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_tor"
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console"
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder"
|
||||
|
|
|
|||
|
|
@ -1,171 +0,0 @@
|
|||
package plg_starter_tunnel
|
||||
|
||||
/*
|
||||
* This plugin was inspired from the Inlets project: https://github.com/alexellis/inlets
|
||||
* which is nothing but a wrapper on: github.com/rancher/remotedialer. As such we're using the parent
|
||||
* project directly, avoiding unecessary dependencies. Interesting alternatives would include:
|
||||
* - https://github.com/koding/tunnel using yamux which looks very interesting
|
||||
* - https://github.com/mmatczuk/go-http-tunnel
|
||||
* - https://github.com/jpillora/chisel
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/remotedialer"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var backoff func() time.Duration = backoff_strategy()
|
||||
|
||||
func init() {
|
||||
tunnel_enable := func() bool {
|
||||
return Config.Get("features.server.tunnel_enable").Schema(func(f *FormElement) *FormElement{
|
||||
if f == nil {
|
||||
f = &FormElement{}
|
||||
}
|
||||
f.Default = false
|
||||
f.Name = "tunnel_enable"
|
||||
f.Type = "enable"
|
||||
f.Target = []string{"tunnel_url"}
|
||||
f.Description = "Enable/Disable tunnel for secure access from the internet"
|
||||
f.Placeholder = "Default: false"
|
||||
return f
|
||||
}).Bool()
|
||||
}
|
||||
tunnel_enable()
|
||||
Config.Get("features.server.tunnel_url").Schema(func(f *FormElement) *FormElement{
|
||||
if f == nil {
|
||||
f = &FormElement{}
|
||||
}
|
||||
f.Id = "tunnel_url"
|
||||
f.Name = "tunnel_url"
|
||||
f.Type = "text"
|
||||
f.Target = []string{}
|
||||
f.Description = "URL from which you can access your filestash. Contact us to make it more friendly"
|
||||
f.ReadOnly = true
|
||||
f.Placeholder = "LOADING... Refresh the page in a few seconds"
|
||||
return f
|
||||
})
|
||||
|
||||
Hooks.Register.Starter(func (r *mux.Router) {
|
||||
Log.Info("[http] starting ...")
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":8334",
|
||||
Handler: r,
|
||||
}
|
||||
go ensureAppHasBooted("http://127.0.0.1:8334/about", "[http] listening on :8334")
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
Log.Error("error: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
Config.Get("features.server.tunnel_url").Set(nil)
|
||||
if tunnel_enable() == false {
|
||||
startTunnel := false
|
||||
onChange := Config.ListenForChange()
|
||||
for {
|
||||
select {
|
||||
case <- onChange.Listener: startTunnel = tunnel_enable()
|
||||
}
|
||||
if startTunnel == true { break }
|
||||
}
|
||||
Config.UnlistenForChange(onChange)
|
||||
}
|
||||
|
||||
Log.Info("[tunnel] starting ...")
|
||||
go func() {
|
||||
for {
|
||||
// Stage1: Register a domain name from which Filestash will be available
|
||||
req, err := http.NewRequest("GET", "https://tunnel.filestash.app/register", nil);
|
||||
if err != nil {
|
||||
Log.Info("[tunnel] registration_request %s", err.Error())
|
||||
time.Sleep(backoff())
|
||||
continue
|
||||
}
|
||||
req.Header.Add("X-Machine-ID", GenerateMachineID())
|
||||
res, err := HTTP.Do(req)
|
||||
if err != nil {
|
||||
Log.Info("[tunnel] registration_error %s", err.Error())
|
||||
time.Sleep(backoff())
|
||||
continue
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
Log.Info("[tunnel] registration_failure HTTP status: %d", res.StatusCode)
|
||||
time.Sleep(backoff())
|
||||
continue
|
||||
}
|
||||
d, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
|
||||
// Stage2: Tunnel data from
|
||||
remotedialer.ClientConnect(
|
||||
"wss://tunnel.filestash.app/connect",
|
||||
http.Header{
|
||||
"X-Machine-ID": []string{ GenerateMachineID() },
|
||||
},
|
||||
nil,
|
||||
func(proto, address string) bool {
|
||||
if proto == "tcp" && address == "127.0.0.1:8334" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
func(context.Context) error {
|
||||
Log.Info("[tunnel] started %s", string(d))
|
||||
Config.Get("features.server.tunnel_url").Set(string(d))
|
||||
return nil
|
||||
},
|
||||
)
|
||||
Log.Info("[tunnel] closed")
|
||||
time.Sleep(backoff())
|
||||
Log.Info("[tunnel] restarting ...")
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func backoff_strategy() func() time.Duration {
|
||||
var last_error time.Time = time.Now()
|
||||
var last_wait time.Duration = 1 * time.Second
|
||||
|
||||
return func() time.Duration {
|
||||
timeSinceLastError := time.Now().Sub(last_error)
|
||||
last_error = time.Now()
|
||||
if timeSinceLastError < 60 * time.Minute {
|
||||
if last_wait < 60 * time.Second {
|
||||
last_wait *= 2
|
||||
}
|
||||
return last_wait
|
||||
}
|
||||
last_wait = 1 * time.Second
|
||||
return last_wait
|
||||
}
|
||||
}
|
||||
|
||||
func ensureAppHasBooted(address string, message string) {
|
||||
i := 0
|
||||
for {
|
||||
if i > 10 {
|
||||
Log.Warning("[http] didn't boot")
|
||||
break
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
res, err := http.Get(address)
|
||||
if err != nil {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
Log.Info(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue