Add deprecation alerts, deprecate DOCKER_NONROOT in favor of docker --user ... (#327)

* deprecate DOCKER_NONROOT in favor of docker --user ...

* remove redundant named volumes creation in compose
* remove DOCKER_NONROOT from demo env file
* add deprecation warning to docker entrypoint
* update documentation

* Added feature to show deprecation messages in UI and logs
Added deprecation message for Docker_NONROOT
Fixed documentation

---------

Co-authored-by: Marc Ole Bulling <Marc-Ole@gmx.de>
This commit is contained in:
spaghetti-coder
2025-11-04 23:26:04 +02:00
committed by GitHub
parent a1efcdf2cf
commit 84eb70edbb
18 changed files with 210 additions and 41 deletions

View File

@@ -33,4 +33,3 @@ TZ=UTC
#GOKAPI_DISABLE_CORS_CHECK=false
#GOKAPI_LOG_STDOUT=false
#GOKAPI_ENABLE_HOTLINK_VIDEOS=false
#DOCKER_NONROOT=false

View File

@@ -137,6 +137,6 @@ func fileExists(filename string) bool {
// Auto-generated content below, do not modify
// Version codes can be changed in updateVersionNumbers.go
const jsAdminVersion = 12
const jsAdminVersion = 13
const jsE2EVersion = 8
const cssMainVersion = 5

View File

@@ -11,7 +11,7 @@ import (
"strings"
)
const versionJsAdmin = 12
const versionJsAdmin = 13
const versionJsDropzone = 5
const versionJsE2EAdmin = 8
const versionCssMain = 5

View File

@@ -67,6 +67,7 @@ func main() {
initCloudConfig(passedFlags)
go storage.CleanUp(true)
logging.LogStartup()
showDeprecationWarnings()
go webserver.Start()
c := make(chan os.Signal)
@@ -107,6 +108,17 @@ func showVersion(passedFlags flagparser.MainFlags) {
osExit(0)
}
func showDeprecationWarnings() {
for _, dep := range configuration.Environment.ActiveDeprecations {
fmt.Println()
fmt.Println("WARNING, deprecated feature: " + dep.Name)
fmt.Println(dep.Description)
fmt.Println("See " + dep.DocUrl + " for more information.")
fmt.Println()
logging.LogDeprecation(dep)
}
}
func parseBuildSettings(infos []debug.BuildSetting) {
lookups := make(map[string]string)
lookups["-tags"] = "Build Tags"

View File

@@ -11,8 +11,3 @@ services:
restart: always
env_file:
- "./.env"
volumes:
gokapi-data:
gokapi-config:

View File

@@ -1,5 +1,10 @@
#!/bin/sh
#DEPRECATED, see https://gokapi.readthedocs.io/en/latest/setup.html#migration-from-docker-nonroot-to-docker-user
if [ "$DOCKER_NONROOT" = "true" ]; then
# TODO for the next major upgrade version:
# - Remove this code block and leave only exec /app/gokapi "@"
# - Remove gokapi user / group creation in Dockerfile
# - Remove su-exec installation from the Dockerfile
echo "Setting permissions" && \
chown -R gokapi:gokapi /app && \
chmod -R 700 /app && \

View File

@@ -94,7 +94,9 @@ Available environment variables
| | | | |
| | unlimited downloads enabled | | |
+-------------------------------+-------------------------------------------------------------------------------------+-----------------+--------------------------------------+
| DOCKER_NONROOT | Docker only: Runs the binary in the container as a non-root user, if set to "true" | No | false |
| DOCKER_NONROOT | DEPRECATED. See :ref:`setupdocker` section for non-root setup | No | false |
| | | | |
| | Docker only: Runs the binary in the container as a non-root user, if set to "true" | | |
+-------------------------------+-------------------------------------------------------------------------------------+-----------------+--------------------------------------+
| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
| | | | |

View File

@@ -66,6 +66,8 @@ Starting Gokapi
^^^^^^^^^^^^^^^^
.. _setupdocker:
Docker
""""""""""
@@ -77,10 +79,45 @@ With the argument ``-p 127.0.0.1:53842:53842`` the service will only be accessib
Set ``-e TZ=UTC`` to the timezone you are in, e.g. ``-e TZ=Europe/Berlin``.
If you do not want the binary to run as the root user in the container, you can set the environment variable ``DOCKER_NONROOT`` to true.
Please make sure that ``/app/data`` and ``/app/config`` are mounted as volumes (see example above), otherwise you will lose all your data after rebuilding or updating your container.
If you do not want the binary to run as the root user in the container, you can run it with ``--user`` option, like in the following example: ::
docker run --user "1000:1000" -v ./gokapi-data:/app/data -v ./gokapi-config:/app/config -p 127.0.0.1:53842:53842 -e TZ=UTC f0rc3/gokapi:latest
Where ``1000:1000`` are colon separated desired user ID and group ID. Please note the command uses bind mounts instead of named volumes. Make sure that the user has read / write permissions to the volumes directories. You can change the names of ``./gokapi-data`` and ``./gokapi-config`` directories to your liking.
.. _deprecation_nonroot:
Migration from DOCKER_NONROOT to docker --user
***********************************************
With deprecation of ``DOCKER_NONROOT`` environment variable you may want to consider migration of your existing configuration and data to ``docker --user`` option approach. The steps are as follows:
::
# Copy configuration and data from the container to your docker host machine.
# Make sure ./gokapi-config and ./gokapi-data directories are not present before
# the following commands are executed
docker cp gokapi:/app/config ./gokapi-config
docker cp gokapi:/app/data ./gokapi-data
# Remove the current container
docker rm -f gokapi
# Start a new container with the current linux session user
docker run --user "$(id -u):$(id -g)" -v ./gokapi-data:/app/data -v ./gokapi-config:/app/config -p 127.0.0.1:53842:53842 -e TZ=UTC f0rc3/gokapi:latest
Where:
* ``gokapi`` is the container name. For you it can be different.
* ``/app/config`` and ``/app/data`` are directories inside container where configuration and data reside. Can be different, depending on your ``GOKAPI_CONFIG_DIR`` and ``GOKAPI_DATA_DIR`` settings.
This example uses your current user ID and group ID for starting the container. To use a different IDs, replace ``$(id -u)`` with the actual user ID and ``$(id -g)`` with the actual group ID.
Docker Compose
""""""""""""""""
@@ -92,6 +129,8 @@ By default, the container is set to always automatically (re)start when the syst
Then, start the container with the command ``docker compose up -d``
Native Deployment
""""""""""""""""""

View File

@@ -6,6 +6,7 @@ import (
"path"
envParser "github.com/caarlos0/env/v6"
"github.com/forceu/gokapi/internal/environment/deprecation"
"github.com/forceu/gokapi/internal/environment/flagparser"
"github.com/forceu/gokapi/internal/helper"
)
@@ -35,11 +36,21 @@ type Environment struct {
AwsKeyId string `env:"AWS_KEY"`
AwsKeySecret string `env:"AWS_KEY_SECRET"`
AwsEndpoint string `env:"AWS_ENDPOINT"`
ActiveDeprecations []deprecation.Deprecation
}
// New parses the env variables
func New() Environment {
result := Environment{WebserverPort: DefaultPort}
result = parseEnvVars(result)
result = parseFlags(result)
result.ActiveDeprecations = deprecation.GetActive()
return result
}
func parseEnvVars(result Environment) Environment {
err := envParser.Parse(&result, envParser.Options{
Prefix: "GOKAPI_",
})
@@ -50,6 +61,23 @@ func New() Environment {
}
helper.Check(err)
if result.LengthId < 5 {
result.LengthId = 5
}
if result.LengthHotlinkId < 8 {
result.LengthHotlinkId = 8
}
if result.MaxMemory < 5 {
result.MaxMemory = 5
}
if result.MaxFileSize < 1 {
result.MaxFileSize = 5
}
return result
}
func parseFlags(result Environment) Environment {
flags := flagparser.ParseFlags()
if flags.IsPortSet {
result.WebserverPort = flags.Port
@@ -67,8 +95,9 @@ func New() Environment {
if flags.IsConfigPathSet {
result.ConfigPath = flags.ConfigPath
}
if IsDockerInstance() && os.Getenv("TMPDIR") == "" {
err = os.Setenv("TMPDIR", result.DataDir)
err := os.Setenv("TMPDIR", result.DataDir)
helper.Check(err)
}
if result.LengthId < 5 {

View File

@@ -0,0 +1,49 @@
package deprecation
import (
"os"
"strings"
)
type Deprecation struct {
Id string
Name string
Description string
DocUrl string
checkFunction func() bool
}
func (d *Deprecation) IsSet() bool {
if d.checkFunction == nil {
panic("checkFunction is nil")
}
return d.checkFunction()
}
func GetActive() []Deprecation {
result := make([]Deprecation, 0)
for _, deprecation := range availableDeprecations {
if deprecation.IsSet() {
result = append(result, deprecation)
}
}
return result
}
var availableDeprecations = []Deprecation{
{
Id: "dockernonroot",
Name: "Docker Non-Root User",
Description: "Usage of DOCKER_NONROOT is deprecated in favor of docker --user option",
DocUrl: "https://gokapi.readthedocs.io/en/latest/setup.html#migration-from-docker-nonroot-to-docker-user",
checkFunction: isNonRootSet,
},
}
func isNonRootSet() bool {
envVal := os.Getenv("DOCKER_NONROOT")
if envVal == "" || strings.ToLower(envVal) == "false" {
return false
}
return true
}

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/environment/deprecation"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
)
@@ -139,6 +140,13 @@ func LogRestore(file models.File, user models.User) {
createLogEntry(categoryEdit, fmt.Sprintf("%s, ID %s, restored by %s (user #%d)", file.Name, file.Id, user.Name, user.Id), false)
}
// LogDeprecation adds a log entry to indicate that a deprecated feature is being used. Blocking
func LogDeprecation(dep deprecation.Deprecation) {
createLogEntry(categoryWarning, "Deprecated feature: "+dep.Name, true)
createLogEntry(categoryWarning, dep.Description, true)
createLogEntry(categoryWarning, "See "+dep.DocUrl+" for more information.", true)
}
// DeleteLogs removes all logs before the cutoff timestamp and inserts a new log that the user
// deleted the previous logs
func DeleteLogs(userName string, userId int, cutoff int64, r *http.Request) {

View File

@@ -581,14 +581,23 @@ func showAdminMenu(w http.ResponseWriter, r *http.Request) {
panic(err)
}
if configuration.Get().Encryption.Level == encryption.EndToEndEncryption {
config := configuration.Get()
if config.Encryption.Level == encryption.EndToEndEncryption {
e2einfo := database.GetEnd2EndInfo(user.Id)
if !e2einfo.HasBeenSetUp() {
redirect(w, "e2eSetup")
return
}
}
err = templateFolder.ExecuteTemplate(w, "admin", (&AdminView{}).convertGlobalConfig(ViewMain, user))
view := (&AdminView{}).convertGlobalConfig(ViewMain, user)
if len(configuration.Environment.ActiveDeprecations) > 0 {
if user.UserLevel == models.UserLevelSuperAdmin {
view.ShowDeprecationNotice = true
}
}
err = templateFolder.ExecuteTemplate(w, "admin", view)
helper.CheckIgnoreTimeout(err)
}
@@ -656,30 +665,31 @@ type e2ESetupView struct {
// AdminView contains parameters for all admin related pages
type AdminView struct {
Items []models.FileApiOutput
ApiKeys []models.ApiKey
Users []userInfo
ActiveUser models.User
UserMap map[int]*models.User
ServerUrl string
Logs string
PublicName string
SystemKey string
IsAdminView bool
IsDownloadView bool
IsApiView bool
IsLogoutAvailable bool
IsUserTabAvailable bool
EndToEndEncryption bool
IncludeFilename bool
IsInternalAuth bool
MaxFileSize int
ActiveView int
ChunkSize int
MaxParallelUploads int
MinLengthPassword int
TimeNow int64
CustomContent customStatic
Items []models.FileApiOutput
ApiKeys []models.ApiKey
Users []userInfo
ActiveUser models.User
UserMap map[int]*models.User
ServerUrl string
Logs string
PublicName string
SystemKey string
IsAdminView bool
IsDownloadView bool
IsApiView bool
IsLogoutAvailable bool
IsUserTabAvailable bool
EndToEndEncryption bool
IncludeFilename bool
IsInternalAuth bool
ShowDeprecationNotice bool
MaxFileSize int
ActiveView int
ChunkSize int
MaxParallelUploads int
MinLengthPassword int
TimeNow int64
CustomContent customStatic
}
// getUserMap needs to return the map with pointers, otherwise template cannot call

View File

@@ -174,6 +174,10 @@ a:hover {
z-index: 9999;
}
.toastdeprecation {
background-color: #8b0000;
}
.toastnotification.show {
opacity: 1;
pointer-events: auto;

View File

@@ -1 +1 @@
.btn-secondary,.btn-secondary:hover,.btn-secondary:focus{color:#333;text-shadow:none}body{background:url(../../assets/background.jpg)no-repeat 50% fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}td{vertical-align:middle;position:relative}a{color:inherit}a:hover{color:inherit;filter:brightness(80%)}.dropzone{background:#2f343a!important;color:#fff;border-radius:5px}.dropzone:hover{background:#33393f!important;color:#fff;border-radius:5px}.card{margin:0 auto;float:none;margin-bottom:10px;border:2px solid #33393f}.card-body{background-color:#212529;color:#ddd}.card-title{font-weight:900}.admin-input{text-align:center}.form-control:disabled{background:#bababa}.break{flex-basis:100%;height:0}.bd-placeholder-img{font-size:1.125rem;text-anchor:middle;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:768px){.bd-placeholder-img-lg{font-size:3.5rem}.break{flex-basis:0}}.masthead{margin-bottom:2rem}.masthead-brand{margin-bottom:0}.nav-masthead .nav-link{padding:.25rem 0;font-weight:700;color:rgba(255,255,255,.5);background-color:transparent;border-bottom:.25rem solid transparent}.nav-masthead .nav-link:hover,.nav-masthead .nav-link:focus{border-bottom-color:rgba(255,255,255,.25)}.nav-masthead .nav-link+.nav-link{margin-left:1rem}.nav-masthead .active{color:#fff;border-bottom-color:#fff}#qroverlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3)}#qrcode{position:absolute;top:50%;left:50%;margin-top:-105px;margin-left:-105px;width:210px;height:210px;border:5px solid #fff}.toastnotification{pointer-events:none;position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#333;color:#fff;padding:15px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.3);opacity:0;transition:opacity .3s ease-in-out;z-index:9999}.toastnotification.show{opacity:1;pointer-events:auto}.toast-undo{margin-left:20px;color:#4fc3f7;cursor:pointer;text-decoration:underline;font-weight:700;pointer-events:auto}.toast-undo:hover{color:#81d4fa}.toastnotification:not(.show){pointer-events:none!important}.toastnotification:not(.show) .toast-undo{pointer-events:none}.perm-granted{cursor:pointer;color:#0edf00}.perm-notgranted{cursor:pointer;color:#9f9999}.perm-unavailable{color:#525252}.perm-processing{pointer-events:none;color:#e5eb00}.perm-nochange{cursor:default}.prevent-select{-webkit-user-select:none;-ms-user-select:none;user-select:none}.gokapi-dialog{background-color:#212529;color:#ddd}@keyframes subtleHighlight{0%{background-color:#444950}100%{background-color:transparent}}@keyframes subtleHighlightNewJson{0%{background-color:green}100%{background-color:transparent}}.updatedDownloadCount{animation:subtleHighlight .5s ease-out}.newApiKey{animation:subtleHighlightNewJson .7s ease-out}.newUser{animation:subtleHighlightNewJson .7s ease-out}.newItem{animation:subtleHighlightNewJson 1.5s ease-out}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.rowDeleting{animation:fadeOut .3s ease-out forwards}.highlighted-password{background-color:#444;color:#ddd;padding:2px 6px;border-radius:4px;font-weight:700;font-family:monospace;display:inline-block;margin-left:8px;border:1px solid #555}.filename{font-weight:700;font-size:14px;margin-bottom:5px}.upload-progress-container{display:flex;align-items:center}.upload-progress-bar{position:relative;height:10px;background-color:#eee;flex:1;margin-right:10px;border-radius:4px}.upload-progress-bar-progress{position:absolute;top:0;left:0;height:100%;background-color:#0a0;border-radius:4px;transition:width .2s ease-in-out}.upload-progress-info{font-size:12px}.us-container{margin-top:10px;margin-bottom:20px}.uploaderror{font-weight:700;color:red;margin-bottom:5px}.uploads-container{background-color:#2f343a;border:2px solid rgba(0,0,0,.3);border-radius:5px;margin-left:0;margin-right:0;max-width:none;visibility:hidden}
.btn-secondary,.btn-secondary:hover,.btn-secondary:focus{color:#333;text-shadow:none}body{background:url(../../assets/background.jpg)no-repeat 50% fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}td{vertical-align:middle;position:relative}a{color:inherit}a:hover{color:inherit;filter:brightness(80%)}.dropzone{background:#2f343a!important;color:#fff;border-radius:5px}.dropzone:hover{background:#33393f!important;color:#fff;border-radius:5px}.card{margin:0 auto;float:none;margin-bottom:10px;border:2px solid #33393f}.card-body{background-color:#212529;color:#ddd}.card-title{font-weight:900}.admin-input{text-align:center}.form-control:disabled{background:#bababa}.break{flex-basis:100%;height:0}.bd-placeholder-img{font-size:1.125rem;text-anchor:middle;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:768px){.bd-placeholder-img-lg{font-size:3.5rem}.break{flex-basis:0}}.masthead{margin-bottom:2rem}.masthead-brand{margin-bottom:0}.nav-masthead .nav-link{padding:.25rem 0;font-weight:700;color:rgba(255,255,255,.5);background-color:transparent;border-bottom:.25rem solid transparent}.nav-masthead .nav-link:hover,.nav-masthead .nav-link:focus{border-bottom-color:rgba(255,255,255,.25)}.nav-masthead .nav-link+.nav-link{margin-left:1rem}.nav-masthead .active{color:#fff;border-bottom-color:#fff}#qroverlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3)}#qrcode{position:absolute;top:50%;left:50%;margin-top:-105px;margin-left:-105px;width:210px;height:210px;border:5px solid #fff}.toastnotification{pointer-events:none;position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#333;color:#fff;padding:15px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.3);opacity:0;transition:opacity .3s ease-in-out;z-index:9999}.toastdeprecation{background-color:#8b0000}.toastnotification.show{opacity:1;pointer-events:auto}.toast-undo{margin-left:20px;color:#4fc3f7;cursor:pointer;text-decoration:underline;font-weight:700;pointer-events:auto}.toast-undo:hover{color:#81d4fa}.toastnotification:not(.show){pointer-events:none!important}.toastnotification:not(.show) .toast-undo{pointer-events:none}.perm-granted{cursor:pointer;color:#0edf00}.perm-notgranted{cursor:pointer;color:#9f9999}.perm-unavailable{color:#525252}.perm-processing{pointer-events:none;color:#e5eb00}.perm-nochange{cursor:default}.prevent-select{-webkit-user-select:none;-ms-user-select:none;user-select:none}.gokapi-dialog{background-color:#212529;color:#ddd}@keyframes subtleHighlight{0%{background-color:#444950}100%{background-color:transparent}}@keyframes subtleHighlightNewJson{0%{background-color:green}100%{background-color:transparent}}.updatedDownloadCount{animation:subtleHighlight .5s ease-out}.newApiKey{animation:subtleHighlightNewJson .7s ease-out}.newUser{animation:subtleHighlightNewJson .7s ease-out}.newItem{animation:subtleHighlightNewJson 1.5s ease-out}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.rowDeleting{animation:fadeOut .3s ease-out forwards}.highlighted-password{background-color:#444;color:#ddd;padding:2px 6px;border-radius:4px;font-weight:700;font-family:monospace;display:inline-block;margin-left:8px;border:1px solid #555}.filename{font-weight:700;font-size:14px;margin-bottom:5px}.upload-progress-container{display:flex;align-items:center}.upload-progress-bar{position:relative;height:10px;background-color:#eee;flex:1;margin-right:10px;border-radius:4px}.upload-progress-bar-progress{position:absolute;top:0;left:0;height:100%;background-color:#0a0;border-radius:4px;transition:width .2s ease-in-out}.upload-progress-info{font-size:12px}.us-container{margin-top:10px;margin-bottom:20px}.uploaderror{font-weight:700;color:red;margin-bottom:5px}.uploads-container{background-color:#2f343a;border:2px solid rgba(0,0,0,.3);border-radius:5px;margin-left:0;margin-right:0;max-width:none;visibility:hidden}

View File

@@ -966,6 +966,7 @@ function handleUndo(button) {
});
}
function shareUrl(id) {
if (!navigator.share) {
return;
@@ -977,3 +978,13 @@ function shareUrl(id) {
url: url,
})
}
function showDeprecationNotice() {
let notification = document.getElementById("toastDeprecation");
notification.classList.add("show");
setTimeout(() => {
notification.classList.remove("show");
}, 5000);
}

View File

@@ -111,6 +111,9 @@
File deleted: <span id="toastFilename"></span>
<span id="toastUndoButton" class="toast-undo" onclick="handleUndo(this)">Restore</span>
</div>
<div id="toastDeprecation" class="toastnotification toastdeprecation">
Warning! This server is using a deprecated feature. Detailed information can be found in the system logs.
</div>
<div id="qroverlay">
<div id="qrcode"></div>
</div>
@@ -155,6 +158,9 @@
var canReplaceOwnFiles = {{.ActiveUser.HasPermissionReplace}};
setUploadDefaults();
{{ if .ShowDeprecationNotice }}
showDeprecationNotice();
{{ end }}
</script>

View File

@@ -3,7 +3,7 @@
// Specifies the version of JS files, so that the browser doesn't
// use a cached version, if the file has been updated
{{define "js_admin_version"}}12{{end}}
{{define "js_admin_version"}}13{{end}}
{{define "js_dropzone_version"}}5{{end}}
{{define "js_e2eversion"}}8{{end}}
{{define "css_main"}}5{{end}}