freeschema 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/freeschema.js +100 -96
- package/package.json +1 -1
package/bin/freeschema.js
CHANGED
|
@@ -122,9 +122,18 @@ function generateMosquittoPassword(username, password) {
|
|
|
122
122
|
return `${username}:$7$${iterations}$${salt.toString('base64')}$${hash.toString('base64')}\n`;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function isContainerHealthy(containerName) {
|
|
126
|
+
const r = spawnSync('docker', [
|
|
127
|
+
'inspect', '--format', '{{.State.Health.Status}}', containerName
|
|
128
|
+
], { stdio: 'pipe' });
|
|
129
|
+
return (r.stdout || '').toString().trim() === 'healthy';
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
function waitForAccessControl(container) {
|
|
133
|
+
if (isContainerHealthy(container)) return true; // already healthy — no wait needed
|
|
126
134
|
process.stdout.write(' Waiting for access-control-server');
|
|
127
|
-
for (let i = 0; i <
|
|
135
|
+
for (let i = 0; i < 60; i++) { // 60 × 2s = 120s
|
|
136
|
+
if (isContainerHealthy(container)) { process.stdout.write(' ready\n'); return true; }
|
|
128
137
|
const r = spawnSync('docker', [
|
|
129
138
|
'exec', container, 'curl', '-sf', 'http://localhost:7000/health'
|
|
130
139
|
], { stdio: 'pipe' });
|
|
@@ -614,19 +623,6 @@ async function cmdStart() {
|
|
|
614
623
|
console.log(' CLIENT_SECRET is not configured.');
|
|
615
624
|
console.log(' Setting up admin credentials…');
|
|
616
625
|
console.log('─────────────────────────────────────────');
|
|
617
|
-
|
|
618
|
-
// Wait for MySQL to be ready before inserting credentials
|
|
619
|
-
try {
|
|
620
|
-
const container = getMysqlContainer();
|
|
621
|
-
const env = readEnv();
|
|
622
|
-
const dbUser = env.DB_USER || env.MYSQL_USER || 'freeschema';
|
|
623
|
-
const dbPass = env.DB_PASSWORD || env.MYSQL_PASSWORD || 'Freeschema@123';
|
|
624
|
-
waitForMySQL(container, dbUser, dbPass);
|
|
625
|
-
} catch (e) {
|
|
626
|
-
console.log(' (skipping credentials setup — MySQL not running)');
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
626
|
await cmdSetupCredentials();
|
|
631
627
|
}
|
|
632
628
|
}
|
|
@@ -772,116 +768,124 @@ function cmdDbRestore() {
|
|
|
772
768
|
|
|
773
769
|
// ── setup-credentials ─────────────────────────────────────────────────────────
|
|
774
770
|
|
|
775
|
-
function
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
771
|
+
function getWicoContainer() {
|
|
772
|
+
const out = runComposeOutput(['ps', '--format', 'json']);
|
|
773
|
+
if (!out) return null;
|
|
774
|
+
const containers = out.split('\n')
|
|
775
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
776
|
+
.filter(Boolean);
|
|
777
|
+
const wico = containers.find(c => c.Service === 'wico-server' || (c.Name || '').includes('wico-server'));
|
|
778
|
+
return wico ? (wico.Name || wico.ID) : null;
|
|
782
779
|
}
|
|
783
780
|
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
'
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
781
|
+
function waitForWicoServer(container, maxAttempts = 60) {
|
|
782
|
+
if (isContainerHealthy(container)) return true; // already healthy — no wait needed
|
|
783
|
+
process.stdout.write(' Waiting for wico-server');
|
|
784
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
785
|
+
if (isContainerHealthy(container)) { process.stdout.write(' ready\n'); return true; }
|
|
786
|
+
const r = spawnSync('docker', [
|
|
787
|
+
'exec', container, 'curl', '-sf', 'http://localhost:7000/health'
|
|
788
|
+
], { stdio: 'pipe' });
|
|
789
|
+
if (r.status === 0) { process.stdout.write(' ready\n'); return true; }
|
|
790
|
+
process.stdout.write('.');
|
|
791
|
+
sleep(3000);
|
|
792
|
+
}
|
|
793
|
+
process.stdout.write(' timed out\n');
|
|
794
|
+
return false;
|
|
795
795
|
}
|
|
796
796
|
|
|
797
797
|
async function cmdSetupCredentials() {
|
|
798
798
|
const envPath = path.join(process.cwd(), '.env');
|
|
799
799
|
if (!fs.existsSync(envPath)) die('.env not found. Run `freeschema init` first.');
|
|
800
800
|
|
|
801
|
-
|
|
802
|
-
const dbName = env.DB_DATABASE || env.MYSQL_DATABASE || 'freeschema_db';
|
|
801
|
+
console.log('\nSetting up CLIENT_ID and CLIENT_SECRET via API\n');
|
|
803
802
|
|
|
804
|
-
|
|
803
|
+
// Find wico-server container
|
|
804
|
+
const container = getWicoContainer();
|
|
805
|
+
if (!container) die('wico-server is not running.\nStart with `freeschema start`.');
|
|
805
806
|
|
|
806
|
-
//
|
|
807
|
-
|
|
808
|
-
|
|
807
|
+
// Wait for wico-server to be healthy
|
|
808
|
+
if (!waitForWicoServer(container)) {
|
|
809
|
+
die('wico-server did not become ready.\nCheck logs: freeschema logs wico-server');
|
|
810
|
+
}
|
|
809
811
|
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
console.log('done');
|
|
812
|
+
// Prompt for admin credentials
|
|
813
|
+
const email = await prompt('Admin email');
|
|
814
|
+
const password = await promptSecret('Admin password');
|
|
815
|
+
if (!email || !password) die('Email and password are required.');
|
|
815
816
|
|
|
816
|
-
// Step
|
|
817
|
-
process.stdout.write('[
|
|
818
|
-
const
|
|
817
|
+
// Step 1: Login
|
|
818
|
+
process.stdout.write('\n[1/3] Logging in… ');
|
|
819
|
+
const loginBody = JSON.stringify({ email, password, application: 'boomconsole' });
|
|
820
|
+
const loginResult = spawnSync('docker', [
|
|
821
|
+
'exec', container, 'curl', '-sf',
|
|
822
|
+
'-X', 'POST', 'http://localhost:7000/api/auth/login',
|
|
823
|
+
'-H', 'Content-Type: application/json',
|
|
824
|
+
'-d', loginBody
|
|
825
|
+
], { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
826
|
+
|
|
827
|
+
if (loginResult.status !== 0) {
|
|
828
|
+
const errText = (loginResult.stderr || '').toString().trim() || 'curl returned non-zero';
|
|
829
|
+
die(`Login request failed: ${errText}`);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
let loginRes;
|
|
833
|
+
try {
|
|
834
|
+
loginRes = JSON.parse((loginResult.stdout || '').toString().trim());
|
|
835
|
+
} catch {
|
|
836
|
+
die('Invalid JSON in login response — is wico-server healthy?');
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Response shape: { status: 200, data: { token, userConcept, ... } }
|
|
840
|
+
const token = loginRes?.data?.token;
|
|
841
|
+
const entityId = loginRes?.data?.userConcept;
|
|
842
|
+
if (!token) {
|
|
843
|
+
die(`Login failed: ${JSON.stringify(loginRes)}`);
|
|
844
|
+
}
|
|
845
|
+
console.log(`done (entity ${entityId})`);
|
|
846
|
+
|
|
847
|
+
// Step 2: Generate client-secret
|
|
848
|
+
process.stdout.write('[2/3] Generating client-secret… ');
|
|
849
|
+
const secretResult = spawnSync('docker', [
|
|
850
|
+
'exec', container, 'curl', '-sf',
|
|
851
|
+
'-X', 'POST', 'http://localhost:7000/api/client-secret',
|
|
852
|
+
'-H', `Authorization: Bearer ${token}`,
|
|
853
|
+
'-H', 'Content-Type: application/json'
|
|
854
|
+
], { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
819
855
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
SET @type_id = (
|
|
825
|
-
SELECT id FROM the_concepts
|
|
826
|
-
WHERE character_value = 'the_client_secret'
|
|
827
|
-
ORDER BY id ASC LIMIT 1
|
|
828
|
-
);
|
|
829
|
-
|
|
830
|
-
-- Insert the secret hash as a new concept
|
|
831
|
-
INSERT INTO the_concepts (
|
|
832
|
-
user_id, category_id, category_user_id, type_id, type_user_id,
|
|
833
|
-
character_value, security_id, security_user_id, access_id, access_user_id,
|
|
834
|
-
session_information_id, session_information_user_id
|
|
835
|
-
) VALUES (
|
|
836
|
-
999, @type_id, 999, @type_id, 999,
|
|
837
|
-
'${hashedSecret}', 4, 999, 4, 999, 999, 999
|
|
838
|
-
);
|
|
839
|
-
|
|
840
|
-
SET @secret_concept_id = LAST_INSERT_ID();
|
|
841
|
-
|
|
842
|
-
-- Look up the connection type for 'the_entity_s_client_secret'
|
|
843
|
-
SET @conn_type_id = (
|
|
844
|
-
SELECT id FROM the_concepts
|
|
845
|
-
WHERE character_value = 'the_entity_s_client_secret'
|
|
846
|
-
ORDER BY id ASC LIMIT 1
|
|
847
|
-
);
|
|
848
|
-
|
|
849
|
-
-- Link entity → secret concept
|
|
850
|
-
INSERT INTO the_connections (
|
|
851
|
-
user_id, of_the_concepts_id, of_the_concepts_user_id,
|
|
852
|
-
type_id, type_user_id, order_id, order_user_id,
|
|
853
|
-
to_the_concepts_id, to_the_concepts_user_id,
|
|
854
|
-
security_id, security_user_id, access_id, access_user_id,
|
|
855
|
-
session_information_id, session_information_user_id
|
|
856
|
-
) VALUES (
|
|
857
|
-
999, @entity_id, 999,
|
|
858
|
-
@conn_type_id, 999, 999, 999,
|
|
859
|
-
@secret_concept_id, 999,
|
|
860
|
-
4, 999, 4, 999, 999, 999
|
|
861
|
-
);
|
|
862
|
-
`;
|
|
856
|
+
if (secretResult.status !== 0) {
|
|
857
|
+
const errText = (secretResult.stderr || '').toString().trim() || 'curl returned non-zero';
|
|
858
|
+
die(`/api/client-secret request failed: ${errText}`);
|
|
859
|
+
}
|
|
863
860
|
|
|
861
|
+
let secretRes;
|
|
864
862
|
try {
|
|
865
|
-
|
|
866
|
-
} catch
|
|
867
|
-
die(
|
|
863
|
+
secretRes = JSON.parse((secretResult.stdout || '').toString().trim());
|
|
864
|
+
} catch {
|
|
865
|
+
die('Invalid JSON in client-secret response');
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Response shape: { success: true, message: "<plainSecretString>" }
|
|
869
|
+
const secret = secretRes?.message;
|
|
870
|
+
if (!secret || typeof secret !== 'string' || !secretRes?.success) {
|
|
871
|
+
die(`Unexpected client-secret response: ${JSON.stringify(secretRes)}`);
|
|
868
872
|
}
|
|
869
873
|
console.log('done');
|
|
870
874
|
|
|
871
|
-
// Step 3:
|
|
875
|
+
// Step 3: Update .env
|
|
872
876
|
process.stdout.write('[3/3] Updating .env… ');
|
|
873
877
|
setEnvVar(envPath, 'CLIENT_ID', String(entityId));
|
|
874
|
-
setEnvVar(envPath, 'CLIENT_SECRET',
|
|
878
|
+
setEnvVar(envPath, 'CLIENT_SECRET', secret);
|
|
875
879
|
console.log('done');
|
|
876
880
|
|
|
877
881
|
console.log(`
|
|
878
882
|
─────────────────────────────────────────
|
|
879
883
|
Credentials saved to .env:
|
|
880
884
|
CLIENT_ID = ${entityId}
|
|
881
|
-
CLIENT_SECRET =
|
|
885
|
+
CLIENT_SECRET = (saved — restart to apply)
|
|
882
886
|
─────────────────────────────────────────
|
|
883
887
|
|
|
884
|
-
Restart
|
|
888
|
+
Restart node-server and node-cache-server to pick up the new secret:
|
|
885
889
|
freeschema restart node-server
|
|
886
890
|
`);
|
|
887
891
|
}
|