Skip to content
This repository was archived by the owner on May 1, 2026. It is now read-only.

Commit 1624432

Browse files
authored
Merge pull request #6 from rwlove/claude/env-config
Configure both services exclusively via environment variables
2 parents 0cfccf1 + 4c8a44d commit 1624432

7 files changed

Lines changed: 178 additions & 122 deletions

File tree

README.md

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,47 +52,44 @@ docker run --name exdiary-api \
5252

5353
# Start the web frontend
5454
docker run --name exdiary-frontend \
55+
-e API_URL=http://<YOUR_HOST_IP>:8851 \
5556
-p 8080:8080 \
56-
ghcr.io/rwlove/exercisediary-frontend \
57-
-a http://<YOUR_HOST_IP>:8851
57+
ghcr.io/rwlove/exercisediary-frontend
5858
```
5959

6060
Then open **http://localhost:8080** in your browser.
6161

6262
## Configuration
6363

64-
Configuration is read from `config.yaml` or environment variables on the **API** server.
64+
Both services are configured exclusively via environment variables. No config file is required.
65+
66+
### API server (`exercisediary-api`)
6567

6668
| Variable | Description | Default |
6769
|---|---|---|
68-
| `AUTH` | Enable session-cookie authentication | `false` |
69-
| `AUTH_EXPIRE` | Session expiration: number + suffix `m`, `h`, `d`, or `M` | `7d` |
70-
| `AUTH_USER` | Username | `""` |
71-
| `AUTH_PASSWORD` | bcrypt-hashed password — [how to generate](docs/BCRYPT.md) | `""` |
70+
| `PORT` | Listen port | `8851` |
7271
| `HOST` | Listen address | `0.0.0.0` |
73-
| `PORT` | API listen port | `8851` |
74-
| `THEME` | Any [Bootswatch](https://bootswatch.com) theme name (lowercase) or extras: `emerald`, `grass`, `grayscale`, `ocean`, `sand`, `wood` | `grass` |
72+
| `DATA_DIR` | SQLite data directory (also settable via `-d` flag) | `/data/ExerciseDiary` |
73+
| `API_KEY` | Require this value on every `X-Api-Key` request header; empty = no auth | `""` |
74+
| `THEME` | Any [Bootswatch](https://bootswatch.com) theme (lowercase) or extras: `emerald`, `grass`, `grayscale`, `ocean`, `sand`, `wood` | `grass` |
7575
| `COLOR` | Background: `light` or `dark` | `light` |
7676
| `HEATCOLOR` | Heatmap cell color | `#03a70c` |
7777
| `PAGESTEP` | Rows per page | `10` |
78-
| `TZ` | Timezone (required for correct date display) | `""` |
79-
80-
## API server options
81-
82-
| Flag | Description | Default |
83-
|---|---|---|
84-
| `-d` | Path to data/config directory | `/data/ExerciseDiary` |
85-
| `-p` | Port to listen on | `8851` |
86-
| `-k` | API key required on `X-Api-Key` header (empty = no auth) | `""` |
78+
| `AUTH` | Enable session-cookie authentication | `false` |
79+
| `AUTH_USER` | Username | `""` |
80+
| `AUTH_PASSWORD` | bcrypt-hashed password — [how to generate](docs/BCRYPT.md) | `""` |
81+
| `AUTH_EXPIRE` | Session expiration: number + suffix `m`, `h`, `d`, or `M` | `7d` |
82+
| `TZ` | Timezone | `""` |
8783

88-
## Frontend options
84+
### Frontend server (`exercisediary-frontend`)
8985

90-
| Flag | Description | Default |
86+
| Variable | Description | Default |
9187
|---|---|---|
92-
| `-a` | Base URL of the API server | `http://localhost:8851` |
93-
| `-p` | Port to listen on | `8080` |
94-
| `-k` | API key sent to the API server | `""` |
95-
| `-n` | Path to local node_modules ([node-bootstrap](https://github.com/aceberg/my-dockerfiles/tree/main/node-bootstrap)) | `""` |
88+
| `PORT` | Listen port | `8080` |
89+
| `API_URL` | Base URL of the API server | `http://localhost:8851` |
90+
| `API_KEY` | `X-Api-Key` value sent to the API (must match API server `API_KEY`) | `""` |
91+
| `NODE_PATH` | URL of a [node-bootstrap](https://github.com/aceberg/my-dockerfiles/tree/main/node-bootstrap) instance for offline use | `""` |
92+
| `TZ` | Timezone | `""` |
9693

9794
## Local network only
9895

@@ -113,6 +110,16 @@ docker run --name exdiary-frontend \
113110

114111
Or use [docker-compose-local.yml](docker-compose-local.yml) to build both images from source.
115112

113+
Set `NODE_PATH` on the frontend to point at the node-bootstrap instance:
114+
115+
```sh
116+
docker run --name exdiary-frontend \
117+
-e API_URL=http://<YOUR_HOST_IP>:8851 \
118+
-e NODE_PATH=http://<YOUR_HOST_IP>:8850 \
119+
-p 8080:8080 \
120+
ghcr.io/rwlove/exercisediary-frontend
121+
```
122+
116123
## Thanks
117124

118125
- All Go packages listed in [dependencies](https://github.com/aceberg/exercisediary/network/dependencies)

cmd/api/main.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,21 @@ package main
22

33
import (
44
"flag"
5+
"os"
56

67
_ "time/tzdata"
78

89
"github.com/aceberg/ExerciseDiary/internal/api"
910
)
1011

11-
const (
12-
defaultDirPath = "/data/ExerciseDiary"
13-
defaultPort = "8851"
14-
defaultAPIKey = ""
15-
)
16-
1712
func main() {
18-
dirPtr := flag.String("d", defaultDirPath, "Path to data/config directory")
19-
portPtr := flag.String("p", defaultPort, "Port to listen on")
20-
keyPtr := flag.String("k", defaultAPIKey, "API key required on X-Api-Key header (empty = no auth)")
13+
// DATA_DIR env var sets the data directory; -d flag overrides it.
14+
dataDir := os.Getenv("DATA_DIR")
15+
if dataDir == "" {
16+
dataDir = "/data/ExerciseDiary"
17+
}
18+
flag.StringVar(&dataDir, "d", dataDir, "Path to data directory (overrides DATA_DIR env var)")
2119
flag.Parse()
2220

23-
api.Start(*dirPtr, *portPtr, *keyPtr)
21+
api.Start(dataDir)
2422
}

cmd/frontend/main.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
package main
22

33
import (
4-
"flag"
4+
"os"
55

66
_ "time/tzdata"
77

88
"github.com/aceberg/ExerciseDiary/internal/store"
99
"github.com/aceberg/ExerciseDiary/internal/web"
1010
)
1111

12-
const (
13-
defaultPort = "8080"
14-
defaultAPIURL = "http://localhost:8851"
15-
defaultAPIKey = ""
16-
defaultNode = ""
17-
)
12+
func envOr(key, fallback string) string {
13+
if v := os.Getenv(key); v != "" {
14+
return v
15+
}
16+
return fallback
17+
}
1818

19+
// All configuration is read from environment variables:
20+
//
21+
// PORT listen port for the web UI (default: 8080)
22+
// API_URL base URL of the API server (default: http://localhost:8851)
23+
// API_KEY X-Api-Key sent to the API (default: "", no auth)
24+
// NODE_PATH path to local node_modules (default: "", use CDN)
1925
func main() {
20-
portPtr := flag.String("p", defaultPort, "Port for the frontend to listen on")
21-
apiURLPtr := flag.String("a", defaultAPIURL, "Base URL of the ExerciseDiary API server")
22-
keyPtr := flag.String("k", defaultAPIKey, "API key sent to the API server (X-Api-Key header)")
23-
nodePtr := flag.String("n", defaultNode, "Path to node_modules (empty = use CDN)")
24-
flag.Parse()
26+
port := envOr("PORT", "8080")
27+
apiURL := envOr("API_URL", "http://localhost:8851")
28+
apiKey := envOr("API_KEY", "")
29+
nodePath := envOr("NODE_PATH", "")
2530

26-
ac := store.NewAPIClient(*apiURLPtr, *keyPtr)
27-
web.GuiWithStore(ac, ac, *portPtr, *nodePtr)
31+
ac := store.NewAPIClient(apiURL, apiKey)
32+
web.GuiWithStore(ac, ac, port, nodePath)
2833
}

docker-compose-local.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,35 @@ services:
66
dockerfile: Dockerfile.api
77
restart: unless-stopped
88
ports:
9-
- 8851:8851
9+
- "8851:8851"
1010
volumes:
11-
- ~/.dockerdata/ExerciseDiary:/data/ExerciseDiary
11+
- ~/.dockerdata/ExerciseDiary:/data/ExerciseDiary
1212
environment:
1313
TZ: America/New_York
14+
PORT: "8851"
15+
HOST: "0.0.0.0"
16+
THEME: "grass"
17+
COLOR: "light"
18+
HEATCOLOR: "#03a70c"
19+
PAGESTEP: "10"
20+
# API_KEY: "changeme"
21+
# AUTH: "true"
22+
# AUTH_USER: "admin"
23+
# AUTH_PASSWORD: ""
24+
# AUTH_EXPIRE: "7d"
1425

1526
exdiary-frontend:
1627
build:
1728
context: .
1829
dockerfile: Dockerfile.frontend
1930
restart: unless-stopped
2031
ports:
21-
- 8080:8080
22-
command: "-a http://exdiary-api:8851"
32+
- "8080:8080"
2333
depends_on:
2434
- exdiary-api
2535
environment:
2636
TZ: America/New_York
37+
PORT: "8080"
38+
API_URL: "http://exdiary-api:8851"
39+
# API_KEY: "changeme"
40+
# NODE_PATH: ""

docker-compose.yml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,33 @@ services:
44
image: ghcr.io/rwlove/exercisediary-api:latest
55
restart: unless-stopped
66
ports:
7-
- 8851:8851
7+
- "8851:8851"
88
volumes:
9-
- ~/.dockerdata/ExerciseDiary:/data/ExerciseDiary
9+
- ~/.dockerdata/ExerciseDiary:/data/ExerciseDiary
1010
environment:
11-
TZ: America/New_York # required, default: ""
11+
TZ: America/New_York
12+
PORT: "8851"
13+
HOST: "0.0.0.0"
14+
THEME: "grass"
15+
COLOR: "light"
16+
HEATCOLOR: "#03a70c"
17+
PAGESTEP: "10"
18+
# API_KEY: "changeme" # uncomment to require X-Api-Key on every request
19+
# AUTH: "true" # uncomment to enable session auth
20+
# AUTH_USER: "admin"
21+
# AUTH_PASSWORD: "" # bcrypt hash — see docs/BCRYPT.md
22+
# AUTH_EXPIRE: "7d"
1223

1324
exdiary-frontend:
1425
image: ghcr.io/rwlove/exercisediary-frontend:latest
1526
restart: unless-stopped
1627
ports:
17-
- 8080:8080
18-
command: "-a http://exdiary-api:8851"
28+
- "8080:8080"
1929
depends_on:
2030
- exdiary-api
2131
environment:
22-
TZ: America/New_York # required, default: ""
32+
TZ: America/New_York
33+
PORT: "8080"
34+
API_URL: "http://exdiary-api:8851"
35+
# API_KEY: "changeme" # must match exdiary-api API_KEY when set
36+
# NODE_PATH: "" # set to local node-bootstrap URL for offline use

internal/api/server.go

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package api
33
import (
44
"log"
55
"net/http"
6+
"os"
67
"strconv"
78

89
"github.com/gin-gonic/gin"
@@ -20,16 +21,32 @@ var (
2021
dataStore store.Store
2122
)
2223

23-
// Start initialises the database, reads config, and begins serving the JSON API.
24-
func Start(dirPath, port, apiKey string) {
25-
confPath := dirPath + "/config.yaml"
26-
check.Path(confPath)
27-
28-
appConfig, authConf = conf.Get(confPath)
29-
appConfig.DirPath = dirPath
30-
appConfig.DBPath = dirPath + "/sqlite.db"
24+
// Start initialises the database, reads config from environment variables,
25+
// and begins serving the JSON API.
26+
//
27+
// dataDir is the only required parameter — it sets where the SQLite database
28+
// lives. All other settings (port, API key, theme, auth, …) are read from
29+
// environment variables:
30+
//
31+
// PORT listen port (default: 8851)
32+
// API_KEY required X-Api-Key value (default: "", no auth)
33+
// HOST listen host (default: 0.0.0.0)
34+
// THEME UI theme (default: grass)
35+
// COLOR light or dark (default: light)
36+
// HEATCOLOR heatmap colour (default: #03a70c)
37+
// PAGESTEP rows per page (default: 10)
38+
// AUTH enable session auth (default: false)
39+
// AUTH_USER username (default: "")
40+
// AUTH_PASSWORD bcrypt password (default: "")
41+
// AUTH_EXPIRE session expiry (default: 7d)
42+
func Start(dataDir string) {
43+
appConfig, authConf = conf.GetFromEnv()
44+
appConfig.DirPath = dataDir
45+
appConfig.DBPath = dataDir + "/sqlite.db"
46+
// ConfPath left empty — Settings changes are in-memory only in env-var mode.
3147
check.Path(appConfig.DBPath)
32-
appConfig.ConfPath = confPath
48+
49+
apiKey := os.Getenv("API_KEY")
3350

3451
log.Println("INFO: starting API server, db =", appConfig.DBPath)
3552

@@ -39,7 +56,7 @@ func Start(dirPath, port, apiKey string) {
3956
gin.SetMode(gin.ReleaseMode)
4057
r := gin.Default()
4158

42-
// API key middleware (optional – skip when apiKey is empty)
59+
// API key middleware (optional – skip when API_KEY is unset)
4360
if apiKey != "" {
4461
r.Use(apiKeyMiddleware(apiKey))
4562
}
@@ -64,7 +81,7 @@ func Start(dirPath, port, apiKey string) {
6481
r.PUT("/api/config", putConfig)
6582
r.PUT("/api/config/auth", putConfigAuth)
6683

67-
address := "0.0.0.0:" + port
84+
address := appConfig.Host + ":" + appConfig.Port
6885
log.Println("=================================== ")
6986
log.Printf("API server at http://%s", address)
7087
log.Println("=================================== ")

0 commit comments

Comments
 (0)