Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager
This commit is contained in:
.dockerignore.gitignore.versionDEV-README.mdJenkinsfileREADME.md
backend
.editorconfig.eslintrc.json.gitignore.golangci.yml.nancy-ignorego.modgo.sumindex.js
.vscode
README.mdTaskfile.ymlapp.jscmd
server
config
db.jsdoc
embed
api_docs
api.swagger.json
main.gocomponents
CertificateAuthorityList.jsonCertificateAuthorityObject.jsonCertificateList.jsonCertificateObject.jsonConfigObject.jsonDNSProviderList.jsonDNSProviderObject.jsonDeletedItemResponse.jsonErrorObject.jsonFilterObject.jsonHealthObject.jsonHostList.jsonHostObject.jsonHostTemplateList.jsonHostTemplateObject.jsonSettingList.jsonSettingObject.jsonSortObject.jsonStreamList.jsonStreamObject.jsonTokenObject.jsonUserAuthObject.jsonUserList.jsonUserObject.json
main.gopaths
certificates-authorities
certificates
config
dns-providers
get.jsonhost-templates
hosts
schema
settings
streams
tokens
users
migrations
nginx
internal
access-list.jsaudit-log.js
knexfile.jsacme
api
context
filters
handler
auth.gocertificate_authorities.gocertificates.goconfig.godns_providers.gohealth.gohelpers.gohost_templates.gohosts.gonot_allowed.gonot_found.goschema.gosettings.gostreams.gotokens.gousers.go
http
middleware
access_control.goauth.goauth_cache.gobody_context.gocors.goenforce_setup.goexpansion.gofilters.gopretty_print.goschema.go
router.gorouter_test.goschema
certificates.gocommon.gocreate_certificate_authority.gocreate_dns_provider.gocreate_host.gocreate_host_template.gocreate_setting.gocreate_stream.gocreate_user.goget_token.goset_auth.goupdate_certificate_authority.goupdate_dns_provider.goupdate_host.goupdate_host_template.goupdate_setting.goupdate_stream.goupdate_user.go
server.gocache
certificate.jsconfig
database
dead-host.jsdnsproviders
common.godns_ad.godns_ali.godns_aws.godns_cf.godns_cloudns.godns_cx.godns_cyon.godns_dgon.godns_dnsimple.godns_dp.godns_duckdns.godns_dyn.godns_dynu.godns_freedns.godns_gandi_livedns.godns_gd.godns_he.godns_infoblox.godns_ispconfig.godns_linode_v4.godns_lua.godns_me.godns_namecom.godns_nsone.godns_pdns.godns_unoeuro.godns_vscale.godns_yandex.go
entity
auth
certificate
certificateauthority
dnsprovider
filters.gofilters_schema.gohost
hosttemplate
lists_query.gosetting
stream
user
errors
host.jsip_ranges.jsjwt
logger
model
nginx.jsnginx
proxy-host.jsredirection-host.jsreport.jssetting.jsstate
stream.jstoken.jstypes
user.jsutil
validator
worker
lib
access.js
logger.jsmigrate.jsaccess
access_lists-create.jsonaccess_lists-delete.jsonaccess_lists-get.jsonaccess_lists-list.jsonaccess_lists-update.jsonauditlog-list.jsoncertificates-create.jsoncertificates-delete.jsoncertificates-get.jsoncertificates-list.jsoncertificates-update.jsondead_hosts-create.jsondead_hosts-delete.jsondead_hosts-get.jsondead_hosts-list.jsondead_hosts-update.jsonpermissions.jsonproxy_hosts-create.jsonproxy_hosts-delete.jsonproxy_hosts-get.jsonproxy_hosts-list.jsonproxy_hosts-update.jsonredirection_hosts-create.jsonredirection_hosts-delete.jsonredirection_hosts-get.jsonredirection_hosts-list.jsonredirection_hosts-update.jsonreports-hosts.jsonroles.jsonsettings-get.jsonsettings-list.jsonsettings-update.jsonstreams-create.jsonstreams-delete.jsonstreams-get.jsonstreams-list.jsonstreams-update.jsonusers-create.jsonusers-delete.jsonusers-get.jsonusers-list.jsonusers-loginas.jsonusers-password.jsonusers-permissions.jsonusers-update.json
error.jsexpress
helpers.jsmigrate_template.jsutils.jsvalidator
migrations
20180618015850_initial.js20180929054513_websockets.js20181019052346_forward_host.js20181113041458_http2_support.js20181213013211_forward_scheme.js20190104035154_disabled.js20190215115310_customlocations.js20190218060101_hsts.js20190227065017_settings.js20200410143839_access_list_client.js20200410143840_access_list_client_fix.js20201014143841_pass_auth.js20210210154702_redirection_scheme.js20210210154703_redirection_status_code.js20210423103500_stream_domain.js20211108145214_regenerate_default_host.js
models
access_list.jsaccess_list_auth.jsaccess_list_client.jsaudit-log.jsauth.jscertificate.jsdead_host.jsnow_helper.jsproxy_host.jsredirection_host.jssetting.jsstream.jstoken.jsuser.jsuser_permission.js
nodemon.jsonpackage.jsonroutes
api
schema
definitions.json
endpoints
access-lists.jsoncertificates.jsondead-hosts.jsonproxy-hosts.jsonredirection-hosts.jsonsettings.jsonstreams.jsontokens.jsonusers.json
examples.jsonindex.jsonscripts
setup.jstemplates
_assets.conf_certificates.conf_exploits.conf_forced_ssl.conf_header_comment.conf_hsts.conf_listen.conf_location.confdead_host.confdefault.confip_ranges.confletsencrypt-request.confproxy_host.confredirection_host.confstream.conf
yarn.lockdocker
Dockerfile
dev
docker-compose.ci.ymldocker-compose.dev.ymlrootfs
bin
etc
cont-init.d
letsencrypt.inilogrotate.d
nginx
conf.d
default.confdev.conf
nginx.confinclude
.gitignoreacme-challenge.confassets.confblock-exploits.confforce-ssl.confip_ranges.confletsencrypt-acme-challenge.confproxy.confresolvers.confssl-ciphers.conf
production.confservices.d
root
var
www
html
docs
frontend
.babelrc.env.development.eslintrc.gitignore.prettierrcREADME.mdcheck-locales.jspackage.json
fonts
feather
globalSetup.jssource-sans-pro
source-sans-pro-v14-latin-ext_latin-700.woffsource-sans-pro-v14-latin-ext_latin-700.woff2source-sans-pro-v14-latin-ext_latin-700italic.woffsource-sans-pro-v14-latin-ext_latin-700italic.woff2source-sans-pro-v14-latin-ext_latin-italic.woffsource-sans-pro-v14-latin-ext_latin-italic.woff2source-sans-pro-v14-latin-ext_latin-regular.woffsource-sans-pro-v14-latin-ext_latin-regular.woff2
html
imagesjest.eslint.jsjs
app
api.jsrouter.js
audit-log
cache.jscontroller.jsdashboard
empty
error
help
i18n.jsmain.jsnginx
access
certificates-list-item.ejscertificates
dead
proxy
access-list-item.ejsdelete.ejsdelete.jsform.ejsform.js
list
location-item.ejslocation.jsmain.ejsmain.jsredirection
stream
settings
tokens.jsui
user
users
i18n
index.jslib
login.jslogin
models
public
images
default-avatar.jpg
index.htmlfavicon
android-chrome-192x192.pngandroid-chrome-512x512.pngapple-touch-icon.pngbrowserconfig.xmlfavicon-16x16.pngfavicon-32x32.pngfavicon.icomstile-150x150.pngsafari-pinned-tab.svgsite.webmanifest
logo-256.pnglogo-bold-horizontal-grey.svglogo-no-text.svglogo-text-horizontal-grey.pnglogo-text-vertical-grey.pngscss
src
App.test.tsxApp.tsxRouter.tsxreact-app-env.d.ts
tsconfig.jsonwebpack.config.jsyarn.lockapi
npm
base.tscreateCertificateAuthority.tscreateDNSProvider.tscreateUser.tsgetCertificateAuthorities.tsgetCertificateAuthority.tsgetCertificates.tsgetDNSProvider.tsgetDNSProviders.tsgetDNSProvidersAcmesh.tsgetHealth.tsgetHostTemplates.tsgetHosts.tsgetSettings.tsgetToken.tsgetUser.tsgetUsers.tshelpers.tsindex.tsmodels.tsrefreshToken.tsresponseTypes.tssetAuth.tssetCertificateAuthority.tssetDNSProvider.tssetUser.ts
components
EmptyList.tsx
Flag
Footer.tsxHelpDrawer
Loader
Loading.tsxLocalePicker.tsxNavigation
Permissions
PrettyButton.tsxSiteWrapper.tsxSpinnerPage.tsxTable
Formatters.tsxRowActionsMenu.tsxTableHelpers.tsTableLayout.tsxTextFilter.tsxindex.tsreact-table-config.d.ts
ThemeSwitcher.tsxUnhealthy.tsxindex.tscontext
declarations.d.tsfonts
source-sans-pro
source-sans-pro-v14-latin-700.woffsource-sans-pro-v14-latin-700.woff2source-sans-pro-v14-latin-700italic.woffsource-sans-pro-v14-latin-700italic.woff2source-sans-pro-v14-latin-italic.woffsource-sans-pro-v14-latin-italic.woff2source-sans-pro-v14-latin-regular.woffsource-sans-pro-v14-latin-regular.woff2
hooks
index.tsuseCertificateAuthorities.tsuseCertificateAuthority.tsuseCertificates.tsuseDNSProvider.tsuseDNSProviders.tsuseDNSProvidersAcmesh.tsuseHealth.tsuseHostTemplates.tsuseHosts.tsuseSettings.tsuseUser.tsuseUsers.ts
img
index.scssindex.tsxlocale
modals
CertificateAuthorityCreateModal.tsxCertificateAuthorityEditModal.tsxChangePasswordModal.tsxDNSProviderCreateModal.tsxProfileModal.tsxSetPasswordModal.tsxUserCreateModal.tsxUserEditModal.tsxindex.ts
modules
pages
AccessLists
AuditLog
CertificateAuthorities
Certificates
DNSProviders
Dashboard
HostTemplates
Hosts
Login
Settings
Setup
Users
styles
theme
global
scripts
.common.shbuildx
ci
docs-buildfrontend-buildfrontend-lintgo-multiarch-wrapperinstall-s6sqlitestart-devtest-devwait-healthytest
46
backend/internal/database/helpers.go
Normal file
46
backend/internal/database/helpers.go
Normal file
@ -0,0 +1,46 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"npm/internal/errors"
|
||||
"npm/internal/model"
|
||||
"npm/internal/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// DateFormat for DateFormat
|
||||
DateFormat = "2006-01-02"
|
||||
// DateTimeFormat for DateTimeFormat
|
||||
DateTimeFormat = "2006-01-02T15:04:05"
|
||||
)
|
||||
|
||||
// GetByQuery returns a row given a query, populating the model given
|
||||
func GetByQuery(model interface{}, query string, params []interface{}) error {
|
||||
db := GetInstance()
|
||||
if db != nil {
|
||||
err := db.Get(model, query, params...)
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
// BuildOrderBySQL takes a `Sort` slice and constructs a query fragment
|
||||
func BuildOrderBySQL(columns []string, sort *[]model.Sort) (string, []model.Sort) {
|
||||
var sortStrings []string
|
||||
var newSort []model.Sort
|
||||
for _, sortItem := range *sort {
|
||||
if util.SliceContainsItem(columns, sortItem.Field) {
|
||||
sortStrings = append(sortStrings, fmt.Sprintf("`%s` %s", sortItem.Field, sortItem.Direction))
|
||||
newSort = append(newSort, sortItem)
|
||||
}
|
||||
}
|
||||
|
||||
if len(sortStrings) > 0 {
|
||||
return fmt.Sprintf("ORDER BY %s", strings.Join(sortStrings, ", ")), newSort
|
||||
}
|
||||
|
||||
return "", newSort
|
||||
}
|
202
backend/internal/database/migrator.go
Normal file
202
backend/internal/database/migrator.go
Normal file
@ -0,0 +1,202 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"npm/embed"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/util"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// MigrationConfiguration options for the migrator.
|
||||
type MigrationConfiguration struct {
|
||||
Table string `json:"table"`
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// Default migrator configuration
|
||||
var mConfiguration = MigrationConfiguration{
|
||||
Table: "migration",
|
||||
}
|
||||
|
||||
// ConfigureMigrator and will return error if missing required fields.
|
||||
func ConfigureMigrator(c *MigrationConfiguration) error {
|
||||
// ensure updates to the config are atomic
|
||||
mConfiguration.mux.Lock()
|
||||
defer mConfiguration.mux.Unlock()
|
||||
if c == nil {
|
||||
return fmt.Errorf("a non nil Configuration is mandatory")
|
||||
}
|
||||
if strings.TrimSpace(c.Table) != "" {
|
||||
mConfiguration.Table = c.Table
|
||||
}
|
||||
mConfiguration.Table = c.Table
|
||||
return nil
|
||||
}
|
||||
|
||||
type afterMigrationComplete func()
|
||||
|
||||
// Migrate will perform the migration from start to finish
|
||||
func Migrate(followup afterMigrationComplete) bool {
|
||||
logger.Info("Migration: Started")
|
||||
|
||||
// Try to connect to the database sleeping for 15 seconds in between
|
||||
var db *sqlx.DB
|
||||
for {
|
||||
db = GetInstance()
|
||||
if db == nil {
|
||||
logger.Warn("Database is unavailable for migration, retrying in 15 seconds")
|
||||
time.Sleep(15 * time.Second)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check for migration table existence
|
||||
if !tableExists(db, mConfiguration.Table) {
|
||||
err := createMigrationTable(db)
|
||||
if err != nil {
|
||||
logger.Error("MigratorError", err)
|
||||
return false
|
||||
}
|
||||
logger.Info("Migration: Migration Table created")
|
||||
}
|
||||
|
||||
// DO MIGRATION
|
||||
migrationCount, migrateErr := performFileMigrations(db)
|
||||
if migrateErr != nil {
|
||||
logger.Error("MigratorError", migrateErr)
|
||||
}
|
||||
|
||||
if migrateErr == nil {
|
||||
logger.Info("Migration: Completed %v migration files", migrationCount)
|
||||
followup()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// createMigrationTable performs a query to create the migration table
|
||||
// with the name specified in the configuration
|
||||
func createMigrationTable(db *sqlx.DB) error {
|
||||
logger.Info("Migration: Creating Migration Table: %v", mConfiguration.Table)
|
||||
// nolint:lll
|
||||
query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%v` (filename TEXT PRIMARY KEY, migrated_on INTEGER NOT NULL DEFAULT 0)", mConfiguration.Table)
|
||||
_, err := db.Exec(query)
|
||||
return err
|
||||
}
|
||||
|
||||
// tableExists will check the database for the existence of the specified table.
|
||||
func tableExists(db *sqlx.DB, tableName string) bool {
|
||||
query := `SELECT CASE name WHEN $1 THEN true ELSE false END AS found FROM sqlite_master WHERE type='table' AND name = $1`
|
||||
|
||||
row := db.QueryRowx(query, tableName)
|
||||
if row == nil {
|
||||
logger.Error("MigratorError", fmt.Errorf("Cannot check if table exists, no row returned: %v", tableName))
|
||||
return false
|
||||
}
|
||||
|
||||
var exists *bool
|
||||
if err := row.Scan(&exists); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false
|
||||
}
|
||||
logger.Error("MigratorError", err)
|
||||
return false
|
||||
}
|
||||
return *exists
|
||||
}
|
||||
|
||||
// performFileMigrations will perform the actual migration,
|
||||
// importing files and updating the database with the rows imported.
|
||||
func performFileMigrations(db *sqlx.DB) (int, error) {
|
||||
var importedCount = 0
|
||||
|
||||
// Grab a list of previously ran migrations from the database:
|
||||
previousMigrations, prevErr := getPreviousMigrations(db)
|
||||
if prevErr != nil {
|
||||
return importedCount, prevErr
|
||||
}
|
||||
|
||||
// List up the ".sql" files on disk
|
||||
err := fs.WalkDir(embed.MigrationFiles, ".", func(file string, d fs.DirEntry, err error) error {
|
||||
if !d.IsDir() {
|
||||
shortFile := filepath.Base(file)
|
||||
|
||||
// Check if this file already exists in the previous migrations
|
||||
// and if so, ignore it
|
||||
if util.SliceContainsItem(previousMigrations, shortFile) {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info("Migration: Importing %v", shortFile)
|
||||
|
||||
sqlContents, ioErr := embed.MigrationFiles.ReadFile(path.Clean(file))
|
||||
if ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
|
||||
sqlString := string(sqlContents)
|
||||
|
||||
tx := db.MustBegin()
|
||||
if _, execErr := tx.Exec(sqlString); execErr != nil {
|
||||
return execErr
|
||||
}
|
||||
if commitErr := tx.Commit(); commitErr != nil {
|
||||
return commitErr
|
||||
}
|
||||
if markErr := markMigrationSuccessful(db, shortFile); markErr != nil {
|
||||
return markErr
|
||||
}
|
||||
|
||||
importedCount++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return importedCount, err
|
||||
}
|
||||
|
||||
// getPreviousMigrations will query the migration table for names
|
||||
// of migrations we can ignore because they should have already
|
||||
// been imported
|
||||
func getPreviousMigrations(db *sqlx.DB) ([]string, error) {
|
||||
var existingMigrations []string
|
||||
// nolint:gosec
|
||||
query := fmt.Sprintf("SELECT filename FROM `%v` ORDER BY filename", mConfiguration.Table)
|
||||
rows, err := db.Queryx(query)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return existingMigrations, nil
|
||||
}
|
||||
return existingMigrations, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var filename *string
|
||||
err := rows.Scan(&filename)
|
||||
if err != nil {
|
||||
return existingMigrations, err
|
||||
}
|
||||
existingMigrations = append(existingMigrations, *filename)
|
||||
}
|
||||
|
||||
return existingMigrations, nil
|
||||
}
|
||||
|
||||
// markMigrationSuccessful will add a row to the migration table
|
||||
func markMigrationSuccessful(db *sqlx.DB, filename string) error {
|
||||
// nolint:gosec
|
||||
query := fmt.Sprintf("INSERT INTO `%v` (filename) VALUES ($1)", mConfiguration.Table)
|
||||
_, err := db.Exec(query, filename)
|
||||
return err
|
||||
}
|
37
backend/internal/database/setup.go
Normal file
37
backend/internal/database/setup.go
Normal file
@ -0,0 +1,37 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"npm/internal/config"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
)
|
||||
|
||||
// CheckSetup Quick check by counting the number of users in the database
|
||||
func CheckSetup() {
|
||||
query := `SELECT COUNT(*) FROM "user" WHERE is_deleted = $1 AND is_disabled = $1 AND is_system = $1`
|
||||
db := GetInstance()
|
||||
|
||||
if db != nil {
|
||||
row := db.QueryRowx(query, false)
|
||||
var totalRows int
|
||||
queryErr := row.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Error("SetupError", queryErr)
|
||||
return
|
||||
}
|
||||
if totalRows == 0 {
|
||||
logger.Warn("No users found, starting in Setup Mode")
|
||||
} else {
|
||||
config.IsSetup = true
|
||||
logger.Info("Application is setup")
|
||||
}
|
||||
|
||||
if config.ErrorReporting {
|
||||
logger.Warn("Error reporting is enabled - Application Errors WILL be sent to Sentry, you can disable this in the Settings interface")
|
||||
}
|
||||
} else {
|
||||
logger.Error("DatabaseError", errors.ErrDatabaseUnavailable)
|
||||
}
|
||||
}
|
74
backend/internal/database/sqlite.go
Normal file
74
backend/internal/database/sqlite.go
Normal file
@ -0,0 +1,74 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"npm/internal/config"
|
||||
"npm/internal/logger"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
// Blank import for Sqlite
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var dbInstance *sqlx.DB
|
||||
|
||||
// NewDB creates a new connection
|
||||
func NewDB() {
|
||||
logger.Info("Creating new DB instance")
|
||||
db := SqliteDB()
|
||||
if db != nil {
|
||||
dbInstance = db
|
||||
}
|
||||
}
|
||||
|
||||
// GetInstance returns an existing or new instance
|
||||
func GetInstance() *sqlx.DB {
|
||||
if dbInstance == nil {
|
||||
NewDB()
|
||||
} else if err := dbInstance.Ping(); err != nil {
|
||||
NewDB()
|
||||
}
|
||||
|
||||
return dbInstance
|
||||
}
|
||||
|
||||
// SqliteDB Create sqlite client
|
||||
func SqliteDB() *sqlx.DB {
|
||||
dbFile := fmt.Sprintf("%s/nginxproxymanager.db", config.Configuration.DataFolder)
|
||||
autocreate(dbFile)
|
||||
db, err := sqlx.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
logger.Error("SqliteError", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// Commit will close and reopen the db file
|
||||
func Commit() *sqlx.DB {
|
||||
if dbInstance != nil {
|
||||
err := dbInstance.Close()
|
||||
if err != nil {
|
||||
logger.Error("DatabaseCloseError", err)
|
||||
}
|
||||
}
|
||||
NewDB()
|
||||
return dbInstance
|
||||
}
|
||||
|
||||
func autocreate(dbFile string) {
|
||||
if _, err := os.Stat(dbFile); os.IsNotExist(err) {
|
||||
// Create it
|
||||
logger.Info("Creating Sqlite DB: %s", dbFile)
|
||||
// nolint: gosec
|
||||
_, err = os.Create(dbFile)
|
||||
if err != nil {
|
||||
logger.Error("FileCreateError", err)
|
||||
}
|
||||
Commit()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user