Skip to content

Commit c28c1cb

Browse files
authored
Update config and readme (#78)
* Update default locations for configs and data. * Update readme to reflect the new default locations. * Remove license from test configs and update data path. --------- Signed-off-by: Gregory Schofield <greg.c.schofield@gmail.com>
1 parent 42c7f97 commit c28c1cb

File tree

13 files changed

+159
-69
lines changed

13 files changed

+159
-69
lines changed

README.md

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ Gemfast is currently distributed in two different ways, a `docker` image and pre
3333

3434
When running Gemfast as a container, its important to mount the following directories:
3535

36-
* /var/gemfast - The directory for the Gemfast data including gems and database
37-
* /etc/gemfast - The directory for the gemfast.hcl config file. It is possible to configure the config file path using `env GEMFAST_CONFIG_FILE=/path/to/my/file.hcl`
36+
* /var/lib/gemfast/data - The directory for the Gemfast data including gems and database
37+
* /etc/gemfast - The directory for the gemfast.hcl config file
3838

3939
```bash
4040
docker run -d --name gemfast-server \
4141
-p 2020:2020 \
42-
-v /etc/gemfast:/etc/gemfast \
43-
-v /var/gemfast:/var/gemfast \
42+
-v ./gemfast.hcl:/etc/gemfast/gemfast.hcl:ro \
43+
-v ./data:/var/lib/gemfast/data \
4444
ghcr.io/gemfast/server:latest
4545
```
4646

@@ -63,11 +63,102 @@ make
6363
./bin/gemfast-server
6464
```
6565

66-
## Docs
66+
## Configuration
6767

68-
You can configure gemfast settings using the `/etc/gemfast/gemfast.hcl` file. There are many options all of which are listed in the documentation.
68+
### Configuration File
6969

70-
For more information see: https://gemfast.io/docs/configuration/
70+
Gemfast is configured using an HCL file. You can customize the location of this file using the `$GEMFAST_CONFIG_FILE` environment variable or by passing the `--config` argument when starting the server.
71+
72+
The places Gemfast automatically checks for configuration files are: `["/etc/gemfast/gemfast.hcl", "~/.config/gemfast/gemfast.hcl"]`
73+
74+
### Configuration Options
75+
76+
```terraform
77+
# ========== gemfast.hcl ==========
78+
79+
# Port to bind the HTTP server to. Defaults to 2020.
80+
port = 2020
81+
82+
# Log level (trace, debug, info, warn, error, fatal, panic)
83+
log_level = "info"
84+
85+
# Base data directory for gemfast. If not set, defaults to platform-specific user data dir.
86+
dir = "/var/lib/gemfast/data"
87+
88+
# Directory to store downloaded gem files.
89+
gem_dir = "/var/lib/gemfast/data/gems"
90+
91+
# Directory to store SQLite database files.
92+
db_dir = "/var/lib/gemfast/data/db"
93+
94+
# Optional path to an ACL file (Casbin policy).
95+
acl_path = "/var/lib/gemfast/data/acl.csv"
96+
97+
# Optional path to an authorization model file (Casbin model).
98+
auth_model_path = "/var/lib/gemfast/data/model.conf"
99+
100+
# Namespace prefix for private gems (default is "private").
101+
private_gems_namespace = "private"
102+
103+
# Disable the web UI if true.
104+
ui_disabled = false
105+
106+
# Disable Prometheus metrics endpoint if true.
107+
metrics_disabled = false
108+
109+
# ==== Mirror block ====
110+
# Define external sources to mirror gems from.
111+
mirror "https://rubygems.org" {
112+
enabled = true # Set to false to disable this mirror
113+
# hostname is auto-derived from upstream, but can be overridden
114+
# hostname = "rubygems.org"
115+
}
116+
117+
# ==== Filter block ====
118+
# Configure regex-based gem allow/deny logic.
119+
filter {
120+
enabled = true # Enable filtering
121+
action = "deny" # Action can be "allow" or "deny"
122+
regex = ["^evil-.*", "^bad-gem$"] # Regex list for filtering gem names
123+
}
124+
125+
# ==== CVE block ====
126+
# Control Ruby CVE integration.
127+
cve {
128+
enabled = true # Enable CVE scanning
129+
max_severity = "high" # Only block gems above this severity
130+
ruby_advisory_db_dir = "/var/lib/gemfast/data/ruby-advisory-db" # Directory to store the CVE DB
131+
}
132+
133+
# ==== Auth block ====
134+
# Configure authentication settings. You can only specify a single auth block.
135+
auth "local" { # can be local, github, or none
136+
bcrypt_cost = 10 # bcrypt cost for hashing passwords
137+
allow_anonymous_read = false # Allow unauthenticated read access
138+
default_user_role = "read" # Default role for newly created users
139+
140+
# If no users are specified, a default admin user will be created and the password written to the logs
141+
user { # repeat this block to add more users
142+
username = "admin"
143+
password = "changeme"
144+
role = "admin"
145+
}
146+
147+
# JWT secret used to sign access tokens (you can provide one or generate it automatically)
148+
secret_key_path = "/var/lib/gemfast/data/.jwt_secret_key"
149+
}
150+
151+
# GitHub OAuth integration
152+
# auth "github" {
153+
154+
# github_client_id = ""
155+
# github_client_secret = ""
156+
# github_user_orgs = ["my-org"] # Restrict access to users in these GitHub orgs
157+
# }
158+
159+
# No auth
160+
# auth "none" {}
161+
```
71162

72163
## UI
73164

internal/config/config.go

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,33 @@ import (
55
"net/url"
66
"os"
77
"os/user"
8+
"path/filepath"
9+
"runtime"
810

11+
"github.com/gemfast/server/internal/utils"
912
"github.com/hashicorp/hcl/v2/hclsimple"
1013
"github.com/rs/zerolog"
1114
"github.com/rs/zerolog/log"
1215
"github.com/sethvargo/go-password/password"
1316
)
1417

1518
type Config struct {
16-
Port int `hcl:"port,optional"`
17-
LogLevel string `hcl:"log_level,optional"`
18-
Dir string `hcl:"dir,optional"`
19-
GemDir string `hcl:"gem_dir,optional"`
20-
DBDir string `hcl:"db_dir,optional"`
21-
ACLPath string `hcl:"acl_path,optional"`
22-
AuthModelPath string `hcl:"auth_model_path,optional"`
23-
PrivateGemsNamespace string `hcl:"private_gems_namespace,optional"`
24-
UIDisabled bool `hcl:"ui_disabled,optional"`
25-
MetricsDisabled bool `hcl:"metrics_disabled,optional"`
19+
user *user.User `hcl:"-"`
20+
Port int `hcl:"port,optional"`
21+
LogLevel string `hcl:"log_level,optional"`
22+
Dir string `hcl:"dir,optional"`
23+
GemDir string `hcl:"gem_dir,optional"`
24+
DBDir string `hcl:"db_dir,optional"`
25+
ACLPath string `hcl:"acl_path,optional"`
26+
AuthModelPath string `hcl:"auth_model_path,optional"`
27+
PrivateGemsNamespace string `hcl:"private_gems_namespace,optional"`
28+
UIDisabled bool `hcl:"ui_disabled,optional"`
29+
MetricsDisabled bool `hcl:"metrics_disabled,optional"`
2630

27-
LicenseKey string `hcl:"license_key,optional"`
28-
Mirrors []*MirrorConfig `hcl:"mirror,block"`
29-
Filter *FilterConfig `hcl:"filter,block"`
30-
CVE *CVEConfig `hcl:"cve,block"`
31-
Auth *AuthConfig `hcl:"auth,block"`
31+
Mirrors []*MirrorConfig `hcl:"mirror,block"`
32+
Filter *FilterConfig `hcl:"filter,block"`
33+
CVE *CVEConfig `hcl:"cve,block"`
34+
Auth *AuthConfig `hcl:"auth,block"`
3235
}
3336

3437
type MirrorConfig struct {
@@ -70,15 +73,16 @@ type LocalUser struct {
7073
}
7174

7275
func NewConfig() *Config {
73-
cfg := Config{}
76+
usr, err := user.Current()
77+
if err != nil {
78+
log.Warn().Err(err).Msg("unable to get the current linux user")
79+
}
80+
cfg := Config{user: usr}
7481
cfgFile := os.Getenv("GEMFAST_CONFIG_FILE")
7582
if cfgFile == "" {
7683
cfgFileTries := []string{"/etc/gemfast/gemfast.hcl"}
77-
usr, err := user.Current()
78-
if err != nil {
79-
log.Warn().Err(err).Msg("unable to get the current linux user")
80-
} else {
81-
cfgFileTries = append(cfgFileTries, fmt.Sprintf("%s/.gemfast/gemfast.hcl", usr.HomeDir))
84+
if usr != nil {
85+
cfgFileTries = append(cfgFileTries, fmt.Sprintf("%s/.config/gemfast/gemfast.hcl", usr.HomeDir))
8286
}
8387
for _, f := range cfgFileTries {
8488
if _, err := os.Stat(f); err == nil {
@@ -95,7 +99,7 @@ func NewConfig() *Config {
9599
return &cfg
96100
}
97101
}
98-
err := hclsimple.DecodeFile(cfgFile, nil, &cfg)
102+
err = hclsimple.DecodeFile(cfgFile, nil, &cfg)
99103
if err != nil {
100104
log.Error().Err(err).Msg(fmt.Sprintf("failed to load configuration file %s", cfgFile))
101105
os.Exit(1)
@@ -121,7 +125,16 @@ func (c *Config) setDefaultServerConfig() {
121125
}
122126
configureLogLevel(c.LogLevel)
123127
if c.Dir == "" {
124-
c.Dir = "/var/gemfast"
128+
if c.user.Username == "root" {
129+
c.Dir = "/var/lib/gemfast/data"
130+
} else if runtime.GOOS == "darwin" {
131+
c.Dir = fmt.Sprintf("%s/Library/Application Support/gemfast", c.user.HomeDir)
132+
} else if runtime.GOOS == "linux" {
133+
c.Dir = fmt.Sprintf("%s/gemfast", os.Getenv("XDG_DATA_HOME"))
134+
if c.Dir == "/gemfast" {
135+
c.Dir = fmt.Sprintf("%s/.local/share/gemfast", c.user.HomeDir)
136+
}
137+
}
125138
}
126139
if c.GemDir == "" {
127140
c.GemDir = fmt.Sprintf("%s/gems", c.Dir)
@@ -174,13 +187,15 @@ func readJWTSecretKeyFromPath(keyPath string) string {
174187
log.Info().Msg("generating a new JWT secret key")
175188
pw, err := password.Generate(64, 10, 0, false, true)
176189
if err != nil {
177-
log.Error().Err(err).Msg("unable to generate a new jwt secret key")
178-
os.Exit(1)
190+
log.Fatal().Err(err).Msg("unable to generate a new jwt secret key")
191+
}
192+
err = utils.MkDirs(filepath.Dir(keyPath))
193+
if err != nil {
194+
log.Fatal().Err(err).Msg("unable to create directory for JWT secret key file")
179195
}
180196
file, err := os.Create(keyPath)
181197
if err != nil {
182-
log.Error().Err(err).Msg("unable to create JWT secret key file")
183-
os.Exit(1)
198+
log.Fatal().Err(err).Msg("unable to create JWT secret key file")
184199
}
185200
defer file.Close()
186201
_, err = file.WriteString(pw)
@@ -193,7 +208,7 @@ func readJWTSecretKeyFromPath(keyPath string) string {
193208
}
194209

195210
func (c *Config) setDefaultAuthConfig() {
196-
defaultJWTSecretKeyPath := ".jwt_secret_key"
211+
defaultJWTSecretKeyPath := fmt.Sprintf("%s/.jwt_secret_key", c.Dir)
197212
if c.Auth == nil {
198213
c.Auth = &AuthConfig{
199214
Type: "local",
@@ -252,6 +267,6 @@ func (c *Config) setDefaultCVEConfig() {
252267
c.CVE.MaxSeverity = "high"
253268
}
254269
if c.CVE.RubyAdvisoryDBDir == "" {
255-
c.CVE.RubyAdvisoryDBDir = "ruby-advisory-db"
270+
c.CVE.RubyAdvisoryDBDir = fmt.Sprintf("%s/ruby-advisory-db", c.Dir)
256271
}
257272
}

internal/db/db.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package db
33
import (
44
"fmt"
55
"net/http"
6-
"os"
76
"path/filepath"
87
"strconv"
98

109
"github.com/gemfast/server/internal/config"
10+
"github.com/gemfast/server/internal/utils"
1111
"github.com/rs/zerolog/log"
1212
"go.etcd.io/bbolt"
1313
bolt "go.etcd.io/bbolt"
@@ -30,7 +30,7 @@ func NewTestDB(boltDB *bolt.DB, cfg *config.Config) *DB {
3030
}
3131

3232
func NewDB(cfg *config.Config) (*DB, error) {
33-
err := os.MkdirAll(cfg.DBDir, os.ModePerm)
33+
err := utils.MkDirs(cfg.DBDir)
3434
if err != nil {
3535
log.Logger.Error().Err(err).Msg(fmt.Sprintf("failed to create db directory %s", cfg.DBDir))
3636
return nil, err

internal/middleware/acl.go

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import (
1111
"github.com/rs/zerolog/log"
1212
)
1313

14-
//go:embed auth_model.conf
14+
//go:embed model.conf
1515
var embeddedAuthModel []byte
1616

17-
//go:embed gemfast_acl.csv
17+
//go:embed acl.csv
1818
var embeddedACL []byte
1919

2020
type ACL struct {
@@ -33,7 +33,8 @@ func NewACL(cfg *config.Config) *ACL {
3333
log.Fatal().Err(err).Msg("failed to get absolute path for acl")
3434
}
3535
} else {
36-
policyPath, err = writeTempFile("gemfast_acl.csv", embeddedACL)
36+
policyPath = fmt.Sprintf("%s/acl.csv", cfg.Dir)
37+
err = os.WriteFile(policyPath, embeddedACL, 0644)
3738
if err != nil {
3839
log.Fatal().Err(err).Msg("failed to write embedded acl to temp file")
3940
}
@@ -45,34 +46,25 @@ func NewACL(cfg *config.Config) *ACL {
4546
log.Fatal().Err(err).Msg("failed to get absolute path for auth_model")
4647
}
4748
} else {
48-
authPath, err = writeTempFile("auth_model.conf", embeddedAuthModel)
49+
authPath = fmt.Sprintf("%s/model.conf", cfg.Dir)
50+
err = os.WriteFile(authPath, embeddedAuthModel, 0644)
4951
if err != nil {
5052
log.Fatal().Err(err).Msg("failed to write embedded auth model to temp file")
5153
}
5254
}
5355

5456
if policyPath == "" || authPath == "" {
55-
log.Fatal().Err(fmt.Errorf("unable to locate auth_model and gemfast_acl")).Msg("failed to find acl files")
57+
log.Fatal().Err(fmt.Errorf("unable to locate model.conf and acl.csv")).Msg("failed to find acl files")
5658
}
5759
acl, err := casbin.NewEnforcer(authPath, policyPath)
5860
if err != nil {
5961
log.Fatal().Err(err).Msg("failed to initialize the acl")
6062
}
61-
log.Info().Str("detail", policyPath).Msg("successfully initialized ACL enforcer")
63+
log.Info().Str("detail", policyPath).Str("detail", authPath).Msg("successfully initialized ACL enforcer")
6264

6365
return &ACL{casbin: acl, cfg: cfg}
6466
}
6567

66-
func writeTempFile(name string, content []byte) (string, error) {
67-
tmp, err := os.CreateTemp("", name)
68-
if err != nil {
69-
return "", err
70-
}
71-
defer tmp.Close()
72-
_, err = tmp.Write(content)
73-
return tmp.Name(), err
74-
}
75-
7668
func (acl *ACL) Enforce(role string, path string, method string) (bool, error) {
7769
return acl.casbin.Enforce(role, path, method)
7870
}

scripts/_functions.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ function start_server() {
44
local BUILD_TYPE="$1"
55
if [[ "$BUILD_TYPE" == "docker" ]]; then
66
docker load -i gemfast*.tar
7-
docker run -d --name gemfast-server -p 2020:2020 -v /etc/gemfast:/etc/gemfast -v /var/gemfast:/var/gemfast goreleaser.ko.local/server:latest start
7+
docker run -d --name gemfast-server -p 2020:2020 -v /etc/gemfast:/etc/gemfast -v ./data:/var/lib/gemfast/data goreleaser.ko.local/server:latest start
88
sleep 5
99
docker ps
1010
docker logs gemfast-server

scripts/run_auth_tests.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ sudo mkdir -p /var/gemfast
1414
sudo chown -R $USER: /etc/gemfast
1515
sudo chown -R $USER: /var/gemfast
1616
cat << CONFIG > /etc/gemfast/gemfast.hcl
17-
license_key = "B7D865-DA12D3-11DA3D-DD81AE-9420D3-V3"
1817
auth "local" {
1918
allow_anonymous_read = false
2019
admin_password = "foobar"
@@ -57,7 +56,6 @@ for gem in *.gem; do
5756
done
5857
sleep 5
5958

60-
sudo ls -la /var/gemfast/gems
6159
sudo rm -f Gemfile Gemfile.lock
6260
cat << CONFIG > Gemfile
6361
source "https://rubygems.org"

scripts/run_cve_tests.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ gem update --system
1212
sudo mkdir -p /etc/gemfast
1313
sudo chown -R $USER: /etc/gemfast
1414
cat << CONFIG > /etc/gemfast/gemfast.hcl
15-
license_key = "B7D865-DA12D3-11DA3D-DD81AE-9420D3-V3"
1615
auth "none" {}
1716
cve {
1817
enabled = true

scripts/run_filter_tests.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ gem update --system
1212
sudo mkdir -p /etc/gemfast
1313
sudo chown -R $USER: /etc/gemfast
1414
cat << CONFIG > /etc/gemfast/gemfast.hcl
15-
license_key = "B7D865-DA12D3-11DA3D-DD81AE-9420D3-V3"
1615
auth "none" {}
1716
filter {
1817
enabled = true
@@ -38,7 +37,6 @@ popd
3837

3938
sudo rm -rf /etc/gemfast/gemfast.hcl
4039
sudo tee /etc/gemfast/gemfast.hcl > /dev/null <<'CONFIG'
41-
license_key = "B7D865-DA12D3-11DA3D-DD81AE-9420D3-V3"
4240
auth "none" {}
4341
filter {
4442
enabled = true

0 commit comments

Comments
 (0)