gui, api: Show internal config and state paths (fixes #8323) (#8324)

* lib/locations: Fix enum values camelCase.

* lib/locations: Remove unused FailuresFile.

* cmd/syncthing: Turn around role of locations storage.

Previously the locations package was used to provide default paths,
possibly with an overridden home directory.  Extra paths supplied on
the command line were handled and passed around in the options object.

To make the changed paths available to any other interested package,
override the location setting from the option if supplied, instead of
vice versa when not supplied.  Adapt code using this to read from the
locations package instead of passing through the options object.

* lib/locations: Refactor showPaths to locations package.

Generate a reusable string in locations.PrettyPrintPaths().
Enumerating all possible locations in different packages is error
prone, so add a new public function to generate the listing as a
string in the locations package.  Adapt cmd/syncthing --paths to use
that instead of its own console output.

* lib/locations: Include CSRF token in pretty printed paths.

* lib/api: New endpoint /rest/system/paths.

The paths should be available for troubleshooting from a running
instance.  Using the --paths CLI option is not easy in some
environments, so expose the locations mapping to a JSON endpoint.

Add utility function ListExpandedPaths() that also filters out any
entries which still contain variable placeholders.

* gui: List runtime paths in separate log viewer tab.

* Wrap paths.

* lib/syncthing: Utilize locations.Get() instead of passing an arg.

* Include base directories, move label to table caption.

* gui: Switch to hard-coded paths instead of iterating over all.

* gui: Break aboutModalView into tabs.

Use tabs to separate authors from included third-party software.

* gui: Move paths from log viewer to about modal.

* lib/locations: Adjust pretty print output order to match GUI.

* gui, authors: Remove additional bot names and fix indent.

The indentation changed because of the tabbed about dialog, fix the
authors script to respect that.

Skip Syncthing*Automation in authors list as well.

* Update AUTHORS list to remove bot names.

* Revert AUTHORS email order change.

* Do not emphasize DB and log file locations.

* Review line wrapping.

* review part 1: strings.Builder, naming

* Rename and extend locations.Set() with error handling.

Remodel the Override() function along the existing SetBaseDir() and
rename it to simply Set().  Make sure to use absolute paths when given
log file or GUI assets override options.  Add proper error reporting
if that goes wrong.

* Remove obsolete comment about empty logfile option.

* Don't filter out unexpanded baseDir placeholders, only ${timestamp}.

* Restore behavior regarding special "-" logfile argument.

If the option is given, but with empty value, assume the no log
file (same as "-").  Don't try to convert the special value to an
absolute path though and document this fact in a comment for the Set()
function.

* Use template to check for location key validity.

* Don't filter out timestamp placeholders.

* lib/api: Remove paths from /rest/system/status.

* lib/ur: Properly initialize map in failure data (fixes #8479)

Co-authored-by: Jakob Borg <jakob@kastelo.net>
This commit is contained in:
André Colomb 2022-08-10 08:25:13 +02:00 committed by GitHub
parent 20dea04aa2
commit 63de838f27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 79 deletions

View File

@ -292,16 +292,25 @@ func (options serveOptions) Run() error {
os.Exit(svcutil.ExitError.AsInt())
}
if options.LogFile == "default" || options.LogFile == "" {
// Treat an explicitly empty log file name as no log file
if options.LogFile == "" {
options.LogFile = "-"
}
if options.LogFile != "default" {
// We must set this *after* expandLocations above.
// Handling an empty value is for backwards compatibility (<1.4.1).
options.LogFile = locations.Get(locations.LogFile)
if err := locations.Set(locations.LogFile, options.LogFile); err != nil {
l.Warnln("Setting log file path:", err)
os.Exit(svcutil.ExitError.AsInt())
}
}
if options.DebugGUIAssetsDir == "" {
if options.DebugGUIAssetsDir != "" {
// The asset dir is blank if STGUIASSETS wasn't set, in which case we
// should look for extra assets in the default place.
options.DebugGUIAssetsDir = locations.Get(locations.GUIAssets)
if err := locations.Set(locations.GUIAssets, options.DebugGUIAssetsDir); err != nil {
l.Warnln("Setting GUI assets path:", err)
os.Exit(svcutil.ExitError.AsInt())
}
}
if options.Version {
@ -310,7 +319,7 @@ func (options serveOptions) Run() error {
}
if options.Paths {
showPaths(options)
fmt.Print(locations.PrettyPaths())
return nil
}
@ -612,7 +621,6 @@ func syncthingMain(options serveOptions) {
}
appOpts := syncthing.Options{
AssetDir: options.DebugGUIAssetsDir,
DeadlockTimeoutS: options.DebugDeadlockTimeout,
NoUpgrade: options.NoUpgrade,
ProfilerAddr: options.DebugProfilerListen,
@ -890,16 +898,6 @@ func cleanConfigDirectory() {
}
}
func showPaths(options serveOptions) {
fmt.Printf("Configuration file:\n\t%s\n\n", locations.Get(locations.ConfigFile))
fmt.Printf("Database directory:\n\t%s\n\n", locations.Get(locations.Database))
fmt.Printf("Device private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.KeyFile), locations.Get(locations.CertFile))
fmt.Printf("HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.HTTPSKeyFile), locations.Get(locations.HTTPSCertFile))
fmt.Printf("Log file:\n\t%s\n\n", options.LogFile)
fmt.Printf("GUI override directory:\n\t%s\n\n", options.DebugGUIAssetsDir)
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
}
func setPauseState(cfgWrapper config.Wrapper, paused bool) {
_, err := cfgWrapper.Modify(func(cfg *config.Configuration) {
for i := range cfg.Devices {

View File

@ -48,7 +48,7 @@ func monitorMain(options serveOptions) {
var dst io.Writer = os.Stdout
logFile := options.LogFile
logFile := locations.Get(locations.LogFile)
if logFile != "-" {
if expanded, err := fs.ExpandTilde(logFile); err == nil {
logFile = expanded

View File

@ -420,6 +420,8 @@ ul.three-columns li, ul.two-columns li {
height: 276px;
}
table.table-auto td,
table.table-auto th,
table.table-condensed td,
table.table-condensed th {
/* for mobile phones to allow linebreaks in long repro folder/shared with

View File

@ -110,7 +110,7 @@
<span class="fas fa-fw fa-question-circle"></span>&nbsp;<span translate>Help</span>
</a>
</li>
<li><a href="" data-toggle="modal" data-target="#about"><span class="far fa-fw fa-heart"></span>&nbsp;<span translate>About</span></a></li>
<li><a href="" ng-click="about.show()"><span class="far fa-fw fa-heart"></span>&nbsp;<span translate>About</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="advanced()"><span class="fas fa-fw fa-cogs"></span>&nbsp;<span translate>Advanced</span></a></li>
<li><a href="" ng-click="logging.show()"><span class="far fa-fw fa-file-alt"></span>&nbsp;<span translate>Logs</span></a></li>

View File

@ -15,15 +15,23 @@
</p>
<p class="text-center" translate>Syncthing is Free and Open Source Software licensed as MPL v2.0.</p>
<hr />
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#about-authors" translate>Authors</a></li>
<li><a data-toggle="tab" href="#about-includes" translate>Included Software</a></li>
<li><a data-toggle="tab" href="#about-paths" translate>Paths</a></li>
</ul>
<div class="tab-content">
<div id="about-authors" class="tab-pane in active">
<h4 class="text-center" translate>The Syncthing Authors</h4>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Tomasz Wilczyński, Wulf Weich, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Devon G. Redekopp, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Kebin Liu, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, bt90, chenrui, chucic, derekriemer, desbma, georgespatton, ghjklw, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, marco-m, mclang, mv1005, otbutz, overkill, perewa, red_led, rubenbe, sec65, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心
</div>
</div>
<hr />
</div>
<div id="about-includes" class="tab-pane">
<p translate>Syncthing includes the following software or portions thereof:</p>
<ul class="list-unstyled two-columns" id="copyright-notices">
<li><a href="http://getbootstrap.com/">Bootstrap</a>, Copyright &copy; 2011-2016 Twitter, Inc.</li>
@ -71,6 +79,51 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Alexander Graf, Alexan
<li>Font Awesome by Dave Gandy - <a href="http://fontawesome.io/">http://fontawesome.io</a></li>
</ul>
</div>
<div id="about-paths" class="tab-pane">
<table class="table table-striped table-auto">
<caption><label translate>Internally used paths:</label></caption>
<tbody>
<tr>
<th translate>User Home</th>
<td><code class="file-path">{{ about.paths['baseDir-userHome'] }}</code></td>
</tr>
<tr>
<th><strong translate>Configuration Directory</strong></th>
<td><code class="file-path"><strong>{{ about.paths['baseDir-config'] }}</strong></code></td>
</tr>
<tr>
<th translate>Configuration File</th>
<td><code class="file-path">{{ about.paths['config'] }}</code></td>
</tr>
<tr>
<th translate>Device Certificate</th>
<td><code class="file-path">{{ about.paths['certFile'] }}</code>
<br /><code class="file-path">{{ about.paths['keyFile'] }}</code></td>
</tr>
<tr>
<th translate>GUI / API HTTPS Certificate</th>
<td><code class="file-path">{{ about.paths['httpsCertFile'] }}</code>
<br /><code class="file-path">{{ about.paths['httpsKeyFile'] }}</code></td>
</tr>
<tr>
<th translate>Database Location</th>
<td><code class="file-path">{{ about.paths['database'] }}</code></td>
</tr>
<tr>
<th translate>Log File</th>
<td><code class="file-path">{{ about.paths['logFile'] }}</code></td>
</tr>
<tr>
<th translate>GUI Override Directory</th>
<td><code class="file-path">{{ about.paths['guiAssets'] }}</code></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>

View File

@ -1399,6 +1399,19 @@ angular.module('syncthing.core')
}
};
$scope.about = {
paths: {},
refreshPaths: function () {
$http.get(urlbase + '/system/paths').success(function (data) {
$scope.about.paths = data;
}).error($scope.emitHTTPError);
},
show: function () {
$scope.about.refreshPaths();
$('#about').modal("show");
},
};
$scope.discardChangedSettings = function () {
$("#discard-changes-confirmation").modal("hide");
$("#settings").off("hide.bs.modal").modal("hide");

View File

@ -268,6 +268,7 @@ func (s *service) Serve(ctx context.Context) error {
restMux.HandlerFunc(http.MethodGet, "/rest/system/connections", s.getSystemConnections) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/discovery", s.getSystemDiscovery) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/error", s.getSystemError) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/paths", s.getSystemPaths) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/ping", s.restPing) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/status", s.getSystemStatus) // -
restMux.HandlerFunc(http.MethodGet, "/rest/system/upgrade", s.getSystemUpgrade) // -
@ -693,6 +694,10 @@ func (*service) restPing(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, map[string]string{"ping": "pong"})
}
func (*service) getSystemPaths(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, locations.ListExpandedPaths())
}
func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
meta, _ := json.Marshal(map[string]string{
"deviceID": s.id.String(),

View File

@ -33,9 +33,8 @@ const (
CsrfTokens LocationEnum = "csrfTokens"
PanicLog LocationEnum = "panicLog"
AuditLog LocationEnum = "auditLog"
GUIAssets LocationEnum = "GUIAssets"
GUIAssets LocationEnum = "guiAssets"
DefFolder LocationEnum = "defFolder"
FailuresFile LocationEnum = "FailuresFile"
)
type BaseDirEnum string
@ -67,6 +66,24 @@ func init() {
}
}
// Set overrides a location to the given path, making sure to it points to an
// absolute path first. Only the special "-" value will be used verbatim.
func Set(locationName LocationEnum, path string) error {
if !filepath.IsAbs(path) && path != "-" {
var err error
path, err = filepath.Abs(path)
if err != nil {
return err
}
}
_, ok := locationTemplates[locationName]
if !ok {
return fmt.Errorf("unknown location: %s", locationName)
}
locations[locationName] = filepath.Clean(path)
return nil
}
func SetBaseDir(baseDirName BaseDirEnum, path string) error {
if !filepath.IsAbs(path) {
var err error
@ -105,7 +122,6 @@ var locationTemplates = map[LocationEnum]string{
AuditLog: "${data}/audit-${timestamp}.log",
GUIAssets: "${config}/gui",
DefFolder: "${userHome}/Sync",
FailuresFile: "${data}/failures-unreported.txt",
}
var locations = make(map[LocationEnum]string)
@ -129,6 +145,32 @@ func expandLocations() error {
return nil
}
// ListExpandedPaths returns a machine-readable mapping of the currently configured locations.
func ListExpandedPaths() map[string]string {
res := make(map[string]string, len(locations))
for key, path := range baseDirs {
res["baseDir-"+string(key)] = path
}
for key, path := range locations {
res[string(key)] = path
}
return res
}
// PrettyPaths returns a nicely formatted, human-readable listing
func PrettyPaths() string {
var b strings.Builder
fmt.Fprintf(&b, "Configuration file:\n\t%s\n\n", Get(ConfigFile))
fmt.Fprintf(&b, "Device private key & certificate files:\n\t%s\n\t%s\n\n", Get(KeyFile), Get(CertFile))
fmt.Fprintf(&b, "GUI / API HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", Get(HTTPSKeyFile), Get(HTTPSCertFile))
fmt.Fprintf(&b, "Database location:\n\t%s\n\n", Get(Database))
fmt.Fprintf(&b, "Log file:\n\t%s\n\n", Get(LogFile))
fmt.Fprintf(&b, "GUI override directory:\n\t%s\n\n", Get(GUIAssets))
fmt.Fprintf(&b, "CSRF tokens file:\n\t%s\n\n", Get(CsrfTokens))
fmt.Fprintf(&b, "Default sync folder directory:\n\t%s\n\n", Get(DefFolder))
return b.String()
}
// defaultConfigDir returns the default configuration directory, as figured
// out by various the environment variables present on each platform, or dies
// trying.

View File

@ -54,7 +54,6 @@ const (
)
type Options struct {
AssetDir string
AuditWriter io.Writer
DeadlockTimeoutS int
NoUpgrade bool
@ -423,7 +422,7 @@ func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscri
summaryService := model.NewFolderSummaryService(a.cfg, m, a.myID, a.evLogger)
a.mainService.Add(summaryService)
apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, a.opts.NoUpgrade)
apiSvc := api.New(a.myID, a.cfg, locations.Get(locations.GUIAssets), tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, a.opts.NoUpgrade)
a.mainService.Add(apiSvc)
if err := apiSvc.WaitForStart(); err != nil {