Skip to content

Commit 06cdbca

Browse files
committed
refactor: align with project_template_go layout + add only_local app-level upload guard
Convention overhaul: - deploy/ → example_files/ (template-style; build.sh bundles into release zip) - example_files/api_show.service systemd unit, template style (User=root, WorkingDirectory=/api_show, ExecStart=/api_show/api_show, no EnvironmentFile) - example_files/install_api_show.sh install/update/uninstall actions, backup-rename old binary + config with _YYYY-MM-DD_HH-MM-SS timestamp - example_files/config_example.yaml yaml schema (secret + upload_token + data_dir + port + only_local) - example_files/nginx.conf thin reverse proxy; /upload-allowlist block removed (now app-side) - deploy/upgrade.sh dropped (install_api_show.sh `update` action covers it) - deploy/install.sh dropped (replaced by example_files/install_api_show.sh) Config-from-yaml refactor: - ac_config/ac_config.go gains FileConfig + LoadConfig(path) + GlobalConfig. Reads ./conf/config.yaml relative to WorkingDirectory; missing file is NOT fatal (defaults populate; env still overrides). - main.go loads yaml at boot, then env vars override file values. - Fixed lowercase env var typos: api_show_PORT/SECRET/TOKEN → API_SHOW_*. App-level LAN guard: - new config flag `only_local` (default true): /upload checks r.RemoteAddr against IsPrivate() / IsLoopback() / IsLinkLocalUnicast(); public-IP requesters are 403'd at the app even with valid Bearer. - X-Forwarded-For deliberately ignored — only_local trusts the kernel peer IP, not headers (forgery-resistant). - nginx.conf simplified: no more /upload location block; /upload gating fully owned by api_show binary. build.sh: - bundles example_files/{api_show.service,install_api_show.sh, config_example.yaml,nginx.conf} into the release zip. Smoke verified: - GOWORK=off go build clean - ./build.sh produces api_show_darwin_release_v0.0.3.zip containing api_show + install_api_show.sh + api_show.service + conf/config.yaml + conf/nginx.conf (7 files, ~12.6MB). Layout now matches github.com/0xYeah/project_template_go convention.
1 parent b51074e commit 06cdbca

11 files changed

Lines changed: 345 additions & 211 deletions

File tree

ac_config/ac_config.go

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,101 @@
1-
// Package ac_config exposes module-level identity constants for api_show.
1+
// Package ac_config exposes module-level identity constants + the runtime
2+
// FileConfig loader for api_show.
23
//
34
// Convention shared with certa_base/cb_config, certa_ame/ame_config,
45
// certa_cg/cg_config, certa_full/full_config: identity constants below
56
// are auto-bumped by ./git_tag.sh; do not edit by hand.
67
package ac_config
78

9+
import (
10+
"fmt"
11+
"os"
12+
13+
"gopkg.in/yaml.v3"
14+
)
15+
816
const (
917
ProjectName = "api_show"
1018
ProjectVersion = "v0.0.3"
1119

1220
// ListenPort is the default HTTP listen port for the api_show portal.
13-
// Overridable via api_show_PORT env at boot.
21+
// Overridable via API_SHOW_PORT env at boot, or `port` in config.yaml.
1422
ListenPort = 12110
1523

1624
// DefaultDataDir holds uploaded openapi specs at runtime. Overridable via
17-
// API_SHOW_DATA env. NOT embedded — content updates require zero rebuild.
25+
// API_SHOW_DATA env or `data_dir` in config.yaml. NOT embedded — content
26+
// updates require zero rebuild.
1827
DefaultDataDir = "./data"
1928
)
29+
30+
// FileConfig is the on-disk configuration shape (conf/config.yaml).
31+
//
32+
// Resolution order at boot (later wins): defaults → file → env vars.
33+
type FileConfig struct {
34+
// Secret is the HMAC cookie key + TOTP key derivation salt. >=16 bytes.
35+
Secret string `yaml:"secret" json:"secret"`
36+
37+
// UploadToken is the Bearer token /upload accepts.
38+
UploadToken string `yaml:"upload_token" json:"upload_token"`
39+
40+
// DataDir is where uploaded openapi specs + totp_blobs/ live.
41+
// Empty → DefaultDataDir.
42+
DataDir string `yaml:"data_dir" json:"data_dir"`
43+
44+
// Port is the HTTP listen port. Zero → ListenPort.
45+
Port int `yaml:"port" json:"port"`
46+
47+
// OnlyLocal restricts /upload to clients whose remote address is in a
48+
// private / loopback / link-local IP range (LAN-only mode). Public-IP
49+
// requesters are 403'd at the app layer regardless of Bearer token —
50+
// belt-and-suspenders for portals that should never face the open internet.
51+
//
52+
// Default: true (LAN-only). Set false to allow public-net uploads
53+
// (still gated by Bearer token + nginx in front).
54+
OnlyLocal bool `yaml:"only_local" json:"only_local"`
55+
}
56+
57+
// GlobalConfig is the loaded configuration. Nil before LoadConfig is called;
58+
// callers should guard or rely on env-only fallback paths.
59+
var GlobalConfig *FileConfig
60+
61+
// DefaultFileConfig returns the in-code defaults — same shape an empty
62+
// config.yaml would produce after merging with built-in defaults.
63+
func DefaultFileConfig() *FileConfig {
64+
return &FileConfig{
65+
DataDir: DefaultDataDir,
66+
Port: ListenPort,
67+
OnlyLocal: true,
68+
}
69+
}
70+
71+
// LoadConfig parses path as YAML into GlobalConfig. Missing file is NOT an
72+
// error — caller falls back to env-only behavior with sensible defaults.
73+
//
74+
// path == "" → "./conf/config.yaml" (relative to WorkingDirectory).
75+
func LoadConfig(path string) error {
76+
if path == "" {
77+
path = "./conf/config.yaml"
78+
}
79+
cfg := DefaultFileConfig()
80+
81+
buf, err := os.ReadFile(path)
82+
if err != nil {
83+
if os.IsNotExist(err) {
84+
GlobalConfig = cfg
85+
return nil
86+
}
87+
return fmt.Errorf("ac_config: read %s: %w", path, err)
88+
}
89+
if err := yaml.Unmarshal(buf, cfg); err != nil {
90+
return fmt.Errorf("ac_config: parse %s: %w", path, err)
91+
}
92+
// Re-fill zero values from defaults (yaml.Unmarshal won't touch absent fields).
93+
if cfg.DataDir == "" {
94+
cfg.DataDir = DefaultDataDir
95+
}
96+
if cfg.Port == 0 {
97+
cfg.Port = ListenPort
98+
}
99+
GlobalConfig = cfg
100+
return nil
101+
}

build.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ function toBuild() {
6262
go build -o "${out_dir}/${product_name}" -trimpath -ldflags "${ld_flag_master}" .
6363
chmod a+x "${out_dir}/${product_name}"
6464

65-
# Optional resourcescopy when present so the zip is self-installable.
66-
[[ -f "./deploy/${product_name}.service" ]] && cp "./deploy/${product_name}.service" "${out_dir}/"
67-
[[ -f "./deploy/install.sh" ]] && cp "./deploy/install.sh" "${out_dir}/install_${product_name}.sh"
68-
[[ -f "./deploy/upgrade.sh" ]] && cp "./deploy/upgrade.sh" "${out_dir}/upgrade_${product_name}.sh"
69-
[[ -f "./deploy/nginx.conf" ]] && cp "./deploy/nginx.conf" "${out_dir}/conf/nginx.conf"
65+
# Bundle example_files/service + installer + config sample + optional nginx.
66+
[[ -f "./example_files/${product_name}.service" ]] && cp "./example_files/${product_name}.service" "${out_dir}/"
67+
[[ -f "./example_files/install_${product_name}.sh" ]] && cp "./example_files/install_${product_name}.sh" "${out_dir}/install_${product_name}.sh"
68+
[[ -f "./example_files/config_example.yaml" ]] && cp "./example_files/config_example.yaml" "${out_dir}/conf/config.yaml"
69+
[[ -f "./example_files/nginx.conf" ]] && cp "./example_files/nginx.conf" "${out_dir}/conf/nginx.conf"
7070

7171
package_files
7272
}

deploy/api_show.service

Lines changed: 0 additions & 34 deletions
This file was deleted.

deploy/install.sh

Lines changed: 0 additions & 87 deletions
This file was deleted.

deploy/nginx.conf

Lines changed: 0 additions & 36 deletions
This file was deleted.

deploy/upgrade.sh

Lines changed: 0 additions & 37 deletions
This file was deleted.

example_files/api_show.service

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[Unit]
2+
Description=api_show service
3+
After=network-online.target
4+
Wants=network-online.target
5+
6+
[Service]
7+
Type=simple
8+
User=root
9+
Restart=on-failure
10+
RestartSec=5s
11+
ExecStart=/api_show/api_show
12+
ExecStop=/bin/kill -TERM $MAINPID
13+
WorkingDirectory=/api_show
14+
LimitNOFILE=102400
15+
16+
[Install]
17+
WantedBy=multi-user.target

example_files/config_example.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# /api_show/conf/config.yaml
2+
#
3+
# Edit this file before first start, then `systemctl restart api_show`.
4+
# Env vars (API_SHOW_SECRET / TOKEN / DATA / PORT) override yaml fields if set.
5+
6+
# REQUIRED — HMAC cookie key + TOTP key derivation salt. >=16 bytes.
7+
secret: "REPLACE_ME_at_least_16_chars"
8+
9+
# REQUIRED for /upload — Bearer token clients (cg / ame / future repos) send.
10+
upload_token: "REPLACE_ME_bearer_token"
11+
12+
# Root dir for uploaded openapi specs + totp_blobs/. Relative to WorkingDirectory.
13+
# Default: ./data
14+
data_dir: "./data"
15+
16+
# HTTP listen port. Default: 12110.
17+
port: 12110
18+
19+
# only_local — restrict /upload to private/loopback/link-local IP ranges
20+
# (true = LAN-only mode; public-IP clients get 403 regardless of Bearer).
21+
# Set false ONLY when intentionally exposing /upload over the open internet
22+
# behind further hardening (nginx + WAF + rate-limit). Default: true.
23+
only_local: true

0 commit comments

Comments
 (0)